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!'
30 'short': 'change tile control 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.'
33 'control_tile_type': {
34 'short': 'change tiles control',
35 'long': 'This mode is the first of two steps to change tile control areas on the map. First enter the tile control character you want to write.'
37 'control_tile_draw': {
38 'short': 'change tiles control',
39 'long': 'This mode is the second of two steps to change tile control areas on the map. Move cursor around the map to draw selected tile control character'
42 'short': 'annotation',
43 '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.'
46 'short': 'edit portal',
47 '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.'
51 '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:'
55 'long': 'Pick your player name.'
57 'waiting_for_server': {
58 'short': 'waiting for server response',
59 'long': 'Waiting for a server response.'
62 'short': 'waiting for server response',
63 'long': 'Waiting for a server response.'
66 'short': 'map edit password',
67 '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.'
70 'short': 'become admin',
71 'long': 'This mode allows you to become admin if you know an admin password.'
75 from ws4py.client import WebSocketBaseClient
76 class WebSocketClient(WebSocketBaseClient):
78 def __init__(self, recv_handler, *args, **kwargs):
79 super().__init__(*args, **kwargs)
80 self.recv_handler = recv_handler
83 def received_message(self, message):
85 message = str(message)
86 self.recv_handler(message)
89 def plom_closed(self):
90 return self.client_terminated
92 from plomrogue.io_tcp import PlomSocket
93 class PlomSocketClient(PlomSocket):
95 def __init__(self, recv_handler, url):
97 self.recv_handler = recv_handler
98 host, port = url.split(':')
99 super().__init__(socket.create_connection((host, port)))
107 for msg in self.recv():
108 if msg == 'NEED_SSL':
109 self.socket = ssl.wrap_socket(self.socket)
111 self.recv_handler(msg)
112 except BrokenSocketConnection:
113 pass # we assume socket will be known as dead by now
115 def cmd_TURN(game, n):
121 game.turn_complete = False
122 cmd_TURN.argtypes = 'int:nonneg'
124 def cmd_LOGIN_OK(game):
125 game.tui.switch_mode('post_login_wait')
126 game.tui.send('GET_GAMESTATE')
127 game.tui.log_msg('@ welcome')
128 cmd_LOGIN_OK.argtypes = ''
130 def cmd_CHAT(game, msg):
131 game.tui.log_msg('# ' + msg)
132 game.tui.do_refresh = True
133 cmd_CHAT.argtypes = 'string'
135 def cmd_PLAYER_ID(game, player_id):
136 game.player_id = player_id
137 cmd_PLAYER_ID.argtypes = 'int:nonneg'
139 def cmd_THING(game, yx, thing_type, thing_id):
140 t = game.get_thing(thing_id)
142 t = ThingBase(game, thing_id)
146 cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type int:nonneg'
148 def cmd_THING_NAME(game, thing_id, name):
149 t = game.get_thing(thing_id)
152 cmd_THING_NAME.argtypes = 'int:nonneg string'
154 def cmd_THING_CHAR(game, thing_id, c):
155 t = game.get_thing(thing_id)
158 cmd_THING_CHAR.argtypes = 'int:nonneg char'
160 def cmd_MAP(game, geometry, size, content):
161 map_geometry_class = globals()['MapGeometry' + geometry]
162 game.map_geometry = map_geometry_class(size)
163 game.map_content = content
164 if type(game.map_geometry) == MapGeometrySquare:
165 game.tui.movement_keys = {
166 game.tui.keys['square_move_up']: 'UP',
167 game.tui.keys['square_move_left']: 'LEFT',
168 game.tui.keys['square_move_down']: 'DOWN',
169 game.tui.keys['square_move_right']: 'RIGHT',
171 elif type(game.map_geometry) == MapGeometryHex:
172 game.tui.movement_keys = {
173 game.tui.keys['hex_move_upleft']: 'UPLEFT',
174 game.tui.keys['hex_move_upright']: 'UPRIGHT',
175 game.tui.keys['hex_move_right']: 'RIGHT',
176 game.tui.keys['hex_move_downright']: 'DOWNRIGHT',
177 game.tui.keys['hex_move_downleft']: 'DOWNLEFT',
178 game.tui.keys['hex_move_left']: 'LEFT',
180 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
182 def cmd_FOV(game, content):
184 cmd_FOV.argtypes = 'string'
186 def cmd_MAP_CONTROL(game, content):
187 game.map_control_content = content
188 cmd_MAP_CONTROL.argtypes = 'string'
190 def cmd_GAME_STATE_COMPLETE(game):
191 if game.tui.mode.name == 'post_login_wait':
192 game.tui.switch_mode('play')
193 if game.tui.mode.shows_info:
194 game.tui.query_info()
195 game.turn_complete = True
196 game.tui.do_refresh = True
197 cmd_GAME_STATE_COMPLETE.argtypes = ''
199 def cmd_PORTAL(game, position, msg):
200 game.portals[position] = msg
201 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
203 def cmd_PLAY_ERROR(game, msg):
204 game.tui.log_msg('? ' + msg)
205 game.tui.flash = True
206 game.tui.do_refresh = True
207 cmd_PLAY_ERROR.argtypes = 'string'
209 def cmd_GAME_ERROR(game, msg):
210 game.tui.log_msg('? game error: ' + msg)
211 game.tui.do_refresh = True
212 cmd_GAME_ERROR.argtypes = 'string'
214 def cmd_ARGUMENT_ERROR(game, msg):
215 game.tui.log_msg('? syntax error: ' + msg)
216 game.tui.do_refresh = True
217 cmd_ARGUMENT_ERROR.argtypes = 'string'
219 def cmd_ANNOTATION_HINT(game, position):
220 game.info_hints += [position]
221 cmd_ANNOTATION_HINT.argtypes = 'yx_tuple:nonneg'
223 def cmd_ANNOTATION(game, position, msg):
224 game.info_db[position] = msg
225 game.tui.restore_input_values()
226 if game.tui.mode.shows_info:
227 game.tui.do_refresh = True
228 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
230 def cmd_TASKS(game, tasks_comma_separated):
231 game.tasks = tasks_comma_separated.split(',')
232 game.tui.mode_edit.legal = 'WRITE' in game.tasks
233 cmd_TASKS.argtypes = 'string'
235 def cmd_THING_TYPE(game, thing_type, symbol_hint):
236 game.thing_types[thing_type] = symbol_hint
237 cmd_THING_TYPE.argtypes = 'string char'
239 def cmd_TERRAIN(game, terrain_char, terrain_desc):
240 game.terrains[terrain_char] = terrain_desc
241 cmd_TERRAIN.argtypes = 'char string'
245 cmd_PONG.argtypes = ''
247 class Game(GameBase):
248 turn_complete = False
252 def __init__(self, *args, **kwargs):
253 super().__init__(*args, **kwargs)
254 self.register_command(cmd_LOGIN_OK)
255 self.register_command(cmd_PONG)
256 self.register_command(cmd_CHAT)
257 self.register_command(cmd_PLAYER_ID)
258 self.register_command(cmd_TURN)
259 self.register_command(cmd_THING)
260 self.register_command(cmd_THING_TYPE)
261 self.register_command(cmd_THING_NAME)
262 self.register_command(cmd_THING_CHAR)
263 self.register_command(cmd_TERRAIN)
264 self.register_command(cmd_MAP)
265 self.register_command(cmd_MAP_CONTROL)
266 self.register_command(cmd_PORTAL)
267 self.register_command(cmd_ANNOTATION)
268 self.register_command(cmd_ANNOTATION_HINT)
269 self.register_command(cmd_GAME_STATE_COMPLETE)
270 self.register_command(cmd_ARGUMENT_ERROR)
271 self.register_command(cmd_GAME_ERROR)
272 self.register_command(cmd_PLAY_ERROR)
273 self.register_command(cmd_TASKS)
274 self.register_command(cmd_FOV)
275 self.map_content = ''
282 def get_string_options(self, string_option_type):
283 if string_option_type == 'map_geometry':
284 return ['Hex', 'Square']
285 elif string_option_type == 'thing_type':
286 return self.thing_types.keys()
289 def get_command(self, command_name):
290 from functools import partial
291 f = partial(self.commands[command_name], self)
292 f.argtypes = self.commands[command_name].argtypes
297 def __init__(self, name, has_input_prompt=False, shows_info=False,
298 is_intro=False, is_single_char_entry=False):
300 self.short_desc = mode_helps[name]['short']
301 self.available_modes = []
302 self.has_input_prompt = has_input_prompt
303 self.shows_info = shows_info
304 self.is_intro = is_intro
305 self.help_intro = mode_helps[name]['long']
306 self.is_single_char_entry = is_single_char_entry
309 def iter_available_modes(self, tui):
310 for mode_name in self.available_modes:
311 mode = getattr(tui, 'mode_' + mode_name)
314 key = tui.keys['switch_to_' + mode.name]
317 def list_available_modes(self, tui):
319 if len(self.available_modes) > 0:
320 msg = 'Other modes available from here:\n'
321 for mode, key in self.iter_available_modes(tui):
322 msg += '[%s] – %s\n' % (key, mode.short_desc)
325 def mode_switch_on_key(self, tui, key_pressed):
326 for mode, key in self.iter_available_modes(tui):
327 if key_pressed == key:
328 tui.switch_mode(mode.name)
333 mode_admin = Mode('admin', has_input_prompt=True)
334 mode_play = Mode('play')
335 mode_study = Mode('study', shows_info=True)
336 mode_edit = Mode('edit', is_single_char_entry=True)
337 mode_control_pw_type = Mode('control_pw_type', is_single_char_entry=True)
338 mode_control_pw_pw = Mode('control_pw_pw', has_input_prompt=True)
339 mode_control_tile_type = Mode('control_tile_type', is_single_char_entry=True)
340 mode_control_tile_draw = Mode('control_tile_draw')
341 mode_annotate = Mode('annotate', has_input_prompt=True, shows_info=True)
342 mode_portal = Mode('portal', has_input_prompt=True, shows_info=True)
343 mode_chat = Mode('chat', has_input_prompt=True)
344 mode_waiting_for_server = Mode('waiting_for_server', is_intro=True)
345 mode_login = Mode('login', has_input_prompt=True, is_intro=True)
346 mode_post_login_wait = Mode('post_login_wait', is_intro=True)
347 mode_password = Mode('password', has_input_prompt=True)
349 def __init__(self, host):
352 self.mode_play.available_modes = ["chat", "study", "edit",
353 "annotate", "portal",
357 self.mode_study.available_modes = ["chat", "play"]
358 self.mode_control_tile_draw.available_modes = ["play"]
362 self.parser = Parser(self.game)
364 self.do_refresh = True
365 self.queue = queue.Queue()
366 self.login_name = None
367 self.map_mode = 'terrain'
368 self.password = 'foo'
369 self.switch_mode('waiting_for_server')
371 'switch_to_chat': 't',
372 'switch_to_play': 'p',
373 'switch_to_password': 'P',
374 'switch_to_annotate': 'M',
375 'switch_to_portal': 'T',
376 'switch_to_study': '?',
377 'switch_to_edit': 'm',
378 'switch_to_admin': 'A',
379 'switch_to_control_pw_type': 'C',
380 'switch_to_control_tile_type': 'Q',
386 'toggle_map_mode': 'M',
387 'hex_move_upleft': 'w',
388 'hex_move_upright': 'e',
389 'hex_move_right': 'd',
390 'hex_move_downright': 'x',
391 'hex_move_downleft': 'y',
392 'hex_move_left': 'a',
393 'square_move_up': 'w',
394 'square_move_left': 'a',
395 'square_move_down': 's',
396 'square_move_right': 'd',
398 if os.path.isfile('config.json'):
399 with open('config.json', 'r') as f:
400 keys_conf = json.loads(f.read())
402 self.keys[k] = keys_conf[k]
403 self.show_help = False
404 self.disconnected = True
405 self.force_instant_connect = True
406 self.input_lines = []
409 curses.wrapper(self.loop)
413 def handle_recv(msg):
419 self.log_msg('@ attempting connect')
420 socket_client_class = PlomSocketClient
421 if self.host.startswith('ws://') or self.host.startswith('wss://'):
422 socket_client_class = WebSocketClient
424 self.socket = socket_client_class(handle_recv, self.host)
425 self.socket_thread = threading.Thread(target=self.socket.run)
426 self.socket_thread.start()
427 self.disconnected = False
428 self.game.thing_types = {}
429 self.game.terrains = {}
430 self.socket.send('TASKS')
431 self.socket.send('TERRAINS')
432 self.socket.send('THING_TYPES')
433 self.switch_mode('login')
434 except ConnectionRefusedError:
435 self.log_msg('@ server connect failure')
436 self.disconnected = True
437 self.switch_mode('waiting_for_server')
438 self.do_refresh = True
441 self.log_msg('@ attempting reconnect')
443 time.sleep(0.1) # FIXME necessitated by some some strange SSL race
444 # conditions with ws4py, find out what exactly
445 self.switch_mode('waiting_for_server')
450 if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
451 raise BrokenSocketConnection
452 self.socket.send(msg)
453 except (BrokenPipeError, BrokenSocketConnection):
454 self.log_msg('@ server disconnected :(')
455 self.disconnected = True
456 self.force_instant_connect = True
457 self.do_refresh = True
459 def log_msg(self, msg):
461 if len(self.log) > 100:
462 self.log = self.log[-100:]
464 def query_info(self):
465 self.send('GET_ANNOTATION ' + str(self.explorer))
467 def restore_input_values(self):
468 if self.mode.name == 'annotate' and self.explorer in self.game.info_db:
469 info = self.game.info_db[self.explorer]
472 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
473 self.input_ = self.game.portals[self.explorer]
474 elif self.mode.name == 'password':
475 self.input_ = self.password
477 def send_tile_control_command(self):
478 self.send('SET_TILE_CONTROL %s %s' %
479 (self.explorer, quote(self.tile_control_char)))
481 def switch_mode(self, mode_name):
482 self.map_mode = 'terrain'
483 self.mode = getattr(self, 'mode_' + mode_name)
484 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
485 player = self.game.get_thing(self.game.player_id)
486 self.explorer = YX(player.position.y, player.position.x)
487 if self.mode.shows_info:
489 elif self.mode.name == 'control_tile_draw':
490 self.send_tile_control_command()
491 self.map_mode = 'control'
492 if self.mode.is_single_char_entry:
493 self.show_help = True
494 if self.mode.name == 'waiting_for_server':
495 self.log_msg('@ waiting for server …')
496 elif self.mode.name == 'login':
498 self.send('LOGIN ' + quote(self.login_name))
500 self.log_msg('@ enter username')
501 elif self.mode.name == 'admin':
502 self.log_msg('@ enter admin password:')
503 elif self.mode.name == 'control_pw_pw':
504 self.log_msg('@ enter tile control password for "%s":' % self.tile_control_char)
505 self.restore_input_values()
507 def loop(self, stdscr):
510 def safe_addstr(y, x, line):
511 if y < self.size.y - 1 or x + len(line) < self.size.x:
512 stdscr.addstr(y, x, line)
513 else: # workaround to <https://stackoverflow.com/q/7063128>
514 cut_i = self.size.x - x - 1
516 last_char = line[cut_i]
517 stdscr.addstr(y, self.size.x - 2, last_char)
518 stdscr.insstr(y, self.size.x - 2, ' ')
519 stdscr.addstr(y, x, cut)
521 def handle_input(msg):
522 command, args = self.parser.parse(msg)
525 def msg_into_lines_of_width(msg, width):
529 for i in range(len(msg)):
530 if x >= width or msg[i] == "\n":
542 def reset_screen_size():
543 self.size = YX(*stdscr.getmaxyx())
544 self.size = self.size - YX(self.size.y % 4, 0)
545 self.size = self.size - YX(0, self.size.x % 4)
546 self.window_width = int(self.size.x / 2)
548 def recalc_input_lines():
549 if not self.mode.has_input_prompt:
550 self.input_lines = []
552 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
555 def move_explorer(direction):
556 target = self.game.map_geometry.move_yx(self.explorer, direction)
558 self.explorer = target
559 if self.mode.shows_info:
561 elif self.mode.name == 'control_tile_draw':
562 self.send_tile_control_command()
568 for line in self.log:
569 lines += msg_into_lines_of_width(line, self.window_width)
572 max_y = self.size.y - len(self.input_lines)
573 for i in range(len(lines)):
574 if (i >= max_y - height_header):
576 safe_addstr(max_y - i - 1, self.window_width, lines[i])
579 if not self.game.turn_complete:
581 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
582 info = 'outside field of view'
583 if self.game.fov[pos_i] == '.':
584 terrain_char = self.game.map_content[pos_i]
586 if terrain_char in self.game.terrains:
587 terrain_desc = self.game.terrains[terrain_char]
588 info = 'TERRAIN: "%s" / %s\n' % (terrain_char, terrain_desc)
589 protection = self.game.map_control_content[pos_i]
590 if protection == '.':
591 protection = 'unprotected'
592 info = 'PROTECTION: %s\n' % protection
593 for t in self.game.things:
594 if t.position == self.explorer:
595 info += 'THING: %s / %s' % (t.type_,
596 self.game.thing_types[t.type_])
597 if hasattr(t, 'player_char'):
598 info += t.player_char
599 if hasattr(t, 'name'):
600 info += ' (%s)' % t.name
602 if self.explorer in self.game.portals:
603 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
605 info += 'PORTAL: (none)\n'
606 if self.explorer in self.game.info_db:
607 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
609 info += 'ANNOTATION: waiting …'
610 lines = msg_into_lines_of_width(info, self.window_width)
612 for i in range(len(lines)):
613 y = height_header + i
614 if y >= self.size.y - len(self.input_lines):
616 safe_addstr(y, self.window_width, lines[i])
619 y = self.size.y - len(self.input_lines)
620 for i in range(len(self.input_lines)):
621 safe_addstr(y, self.window_width, self.input_lines[i])
625 if not self.game.turn_complete:
627 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
630 help = "hit [%s] for help" % self.keys['help']
631 if self.mode.has_input_prompt:
632 help = "enter /help for help"
633 safe_addstr(1, self.window_width,
634 'MODE: %s – %s' % (self.mode.short_desc, help))
637 if not self.game.turn_complete:
640 map_content = self.game.map_content
641 if self.map_mode == 'control':
642 map_content = self.game.map_control_content
643 for y in range(self.game.map_geometry.size.y):
644 start = self.game.map_geometry.size.x * y
645 end = start + self.game.map_geometry.size.x
646 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
647 if self.map_mode == 'annotations':
648 for p in self.game.info_hints:
649 map_lines_split[p.y][p.x] = 'A '
650 elif self.map_mode == 'terrain':
651 for p in self.game.portals.keys():
652 map_lines_split[p.y][p.x] = 'P '
654 for t in self.game.things:
655 symbol = self.game.thing_types[t.type_]
657 if hasattr(t, 'player_char'):
658 meta_char = t.player_char
659 if t.position in used_positions:
661 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
662 used_positions += [t.position]
663 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
664 map_lines_split[self.explorer.y][self.explorer.x] = '??'
666 if type(self.game.map_geometry) == MapGeometryHex:
668 for line in map_lines_split:
669 map_lines += [indent*' ' + ''.join(line)]
670 indent = 0 if indent else 1
672 for line in map_lines_split:
673 map_lines += [''.join(line)]
674 window_center = YX(int(self.size.y / 2),
675 int(self.window_width / 2))
676 player = self.game.get_thing(self.game.player_id)
677 center = player.position
678 if self.mode.shows_info:
679 center = self.explorer
680 center = YX(center.y, center.x * 2)
681 offset = center - window_center
682 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
684 term_y = max(0, -offset.y)
685 term_x = max(0, -offset.x)
686 map_y = max(0, offset.y)
687 map_x = max(0, offset.x)
688 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
689 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
690 safe_addstr(term_y, term_x, to_draw)
695 content = "%s help\n\n%s\n\n" % (self.mode.short_desc,
696 self.mode.help_intro)
697 if self.mode.name == 'play':
698 content += "Available actions:\n"
699 if 'MOVE' in self.game.tasks:
700 content += "[%s] – move player\n" % ','.join(self.movement_keys)
701 if 'PICK_UP' in self.game.tasks:
702 content += "[%s] – take thing under player\n" % self.keys['take_thing']
703 if 'DROP' in self.game.tasks:
704 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
705 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
706 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
707 content += '[%s] – teleport to other space\n' % self.keys['teleport']
709 elif self.mode.name == 'study':
710 content += 'Available actions:\n'
711 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
712 content += '[%s] – toggle view between terrain, annotations, and password protection areas\n' % self.keys['toggle_map_mode']
714 elif self.mode.name == 'chat':
715 content += '/nick NAME – re-name yourself to NAME\n'
716 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
717 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
718 content += self.mode.list_available_modes(self)
719 for i in range(self.size.y):
721 self.window_width * (not self.mode.has_input_prompt),
722 ' '*self.window_width)
724 for line in content.split('\n'):
725 lines += msg_into_lines_of_width(line, self.window_width)
726 for i in range(len(lines)):
730 self.window_width * (not self.mode.has_input_prompt),
735 if self.mode.has_input_prompt:
738 if self.mode.shows_info:
743 if not self.mode.is_intro:
749 curses.curs_set(False) # hide cursor
750 curses.use_default_colors();
753 self.explorer = YX(0, 0)
756 interval = datetime.timedelta(seconds=5)
757 last_ping = datetime.datetime.now() - interval
759 if self.disconnected and self.force_instant_connect:
760 self.force_instant_connect = False
762 now = datetime.datetime.now()
763 if now - last_ping > interval:
764 if self.disconnected:
774 self.do_refresh = False
777 msg = self.queue.get(block=False)
782 key = stdscr.getkey()
783 self.do_refresh = True
786 self.show_help = False
787 if key == 'KEY_RESIZE':
789 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
790 self.input_ = self.input_[:-1]
791 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
792 self.show_help = True
794 self.restore_input_values()
795 elif self.mode.has_input_prompt and key != '\n': # Return key
797 max_length = self.window_width * self.size.y - len(input_prompt) - 1
798 if len(self.input_) > max_length:
799 self.input_ = self.input_[:max_length]
800 elif key == self.keys['help'] and not self.mode.is_single_char_entry:
801 self.show_help = True
802 elif self.mode.name == 'login' and key == '\n':
803 self.login_name = self.input_
804 self.send('LOGIN ' + quote(self.input_))
806 elif self.mode.name == 'control_pw_pw' and key == '\n':
807 if self.input_ == '':
808 self.log_msg('@ aborted')
810 self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
812 self.switch_mode('play')
813 elif self.mode.name == 'password' and key == '\n':
814 if self.input_ == '':
816 self.password = self.input_
818 self.switch_mode('play')
819 elif self.mode.name == 'admin' and key == '\n':
820 self.send('BECOME_ADMIN ' + quote(self.input_))
822 self.switch_mode('play')
823 elif self.mode.name == 'chat' and key == '\n':
824 if self.input_ == '':
826 if self.input_[0] == '/': # FIXME fails on empty input
827 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
828 self.switch_mode('play')
829 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
830 self.switch_mode('study')
831 elif self.input_.startswith('/nick'):
832 tokens = self.input_.split(maxsplit=1)
834 self.send('NICK ' + quote(tokens[1]))
836 self.log_msg('? need login name')
838 self.log_msg('? unknown command')
840 self.send('ALL ' + quote(self.input_))
842 elif self.mode.name == 'annotate' and key == '\n':
843 if self.input_ == '':
845 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
846 quote(self.password)))
848 self.switch_mode('play')
849 elif self.mode.name == 'portal' and key == '\n':
850 if self.input_ == '':
852 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
853 quote(self.password)))
855 self.switch_mode('play')
856 elif self.mode.name == 'study':
857 if self.mode.mode_switch_on_key(self, key):
859 elif key == self.keys['toggle_map_mode']:
860 if self.map_mode == 'terrain':
861 self.map_mode = 'annotations'
862 elif self.map_mode == 'annotations':
863 self.map_mode = 'control'
865 self.map_mode = 'terrain'
866 elif key in self.movement_keys:
867 move_explorer(self.movement_keys[key])
868 elif self.mode.name == 'play':
869 if self.mode.mode_switch_on_key(self, key):
871 if key == self.keys['flatten'] and\
872 'FLATTEN_SURROUNDINGS' in self.game.tasks:
873 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
874 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
875 self.send('TASK:PICK_UP')
876 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
877 self.send('TASK:DROP')
878 elif key == self.keys['teleport']:
879 player = self.game.get_thing(self.game.player_id)
880 if player.position in self.game.portals:
881 self.host = self.game.portals[player.position]
885 self.log_msg('? not standing on portal')
886 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
887 self.send('TASK:MOVE ' + self.movement_keys[key])
888 elif self.mode.name == 'edit':
889 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
890 self.switch_mode('play')
891 elif self.mode.name == 'control_pw_type':
892 self.tile_control_char = key
893 self.switch_mode('control_pw_pw')
894 elif self.mode.name == 'control_tile_type':
895 self.tile_control_char = key
896 self.switch_mode('control_tile_draw')
897 elif self.mode.name == 'control_tile_draw':
898 if self.mode.mode_switch_on_key(self, key):
900 elif key in self.movement_keys:
901 move_explorer(self.movement_keys[key])
903 #TUI('localhost:5000')
904 TUI('wss://plomlompom.com/rogue_chat/')