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': 'password input',
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":
540 def reset_screen_size():
541 self.size = YX(*stdscr.getmaxyx())
542 self.size = self.size - YX(self.size.y % 4, 0)
543 self.size = self.size - YX(0, self.size.x % 4)
544 self.window_width = int(self.size.x / 2)
546 def recalc_input_lines():
547 if not self.mode.has_input_prompt:
548 self.input_lines = []
550 self.input_lines = msg_into_lines_of_width(input_prompt + self.input_,
553 def move_explorer(direction):
554 target = self.game.map_geometry.move_yx(self.explorer, direction)
556 self.explorer = target
557 if self.mode.shows_info:
559 elif self.mode.name == 'control_tile_draw':
560 self.send_tile_control_command()
566 for line in self.log:
567 lines += msg_into_lines_of_width(line, self.window_width)
570 max_y = self.size.y - len(self.input_lines)
571 for i in range(len(lines)):
572 if (i >= max_y - height_header):
574 safe_addstr(max_y - i - 1, self.window_width, lines[i])
577 if not self.game.turn_complete:
579 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
580 info = 'outside field of view'
581 if self.game.fov[pos_i] == '.':
582 terrain_char = self.game.map_content[pos_i]
584 if terrain_char in self.game.terrains:
585 terrain_desc = self.game.terrains[terrain_char]
586 info = 'TERRAIN: "%s" / %s\n' % (terrain_char, terrain_desc)
587 protection = self.game.map_control_content[pos_i]
588 if protection == '.':
589 protection = 'unprotected'
590 info = 'PROTECTION: %s\n' % protection
591 for t in self.game.things:
592 if t.position == self.explorer:
593 info += 'THING: %s / %s' % (t.type_,
594 self.game.thing_types[t.type_])
595 if hasattr(t, 'player_char'):
596 info += t.player_char
597 if hasattr(t, 'name'):
598 info += ' (%s)' % t.name
600 if self.explorer in self.game.portals:
601 info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
603 info += 'PORTAL: (none)\n'
604 if self.explorer in self.game.info_db:
605 info += 'ANNOTATION: ' + self.game.info_db[self.explorer]
607 info += 'ANNOTATION: waiting …'
608 lines = msg_into_lines_of_width(info, self.window_width)
610 for i in range(len(lines)):
611 y = height_header + i
612 if y >= self.size.y - len(self.input_lines):
614 safe_addstr(y, self.window_width, lines[i])
617 y = self.size.y - len(self.input_lines)
618 for i in range(len(self.input_lines)):
619 safe_addstr(y, self.window_width, self.input_lines[i])
623 if not self.game.turn_complete:
625 safe_addstr(0, self.window_width, 'TURN: ' + str(self.game.turn))
628 help = "hit [%s] for help" % self.keys['help']
629 if self.mode.has_input_prompt:
630 help = "enter /help for help"
631 safe_addstr(1, self.window_width,
632 'MODE: %s – %s' % (self.mode.short_desc, help))
635 if not self.game.turn_complete:
638 map_content = self.game.map_content
639 if self.map_mode == 'control':
640 map_content = self.game.map_control_content
641 for y in range(self.game.map_geometry.size.y):
642 start = self.game.map_geometry.size.x * y
643 end = start + self.game.map_geometry.size.x
644 map_lines_split += [[c + ' ' for c in map_content[start:end]]]
645 if self.map_mode == 'annotations':
646 for p in self.game.info_hints:
647 map_lines_split[p.y][p.x] = 'A '
648 elif self.map_mode == 'terrain':
649 for p in self.game.portals.keys():
650 map_lines_split[p.y][p.x] = 'P '
652 for t in self.game.things:
653 symbol = self.game.thing_types[t.type_]
655 if hasattr(t, 'player_char'):
656 meta_char = t.player_char
657 if t.position in used_positions:
659 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
660 used_positions += [t.position]
661 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
662 map_lines_split[self.explorer.y][self.explorer.x] = '??'
664 if type(self.game.map_geometry) == MapGeometryHex:
666 for line in map_lines_split:
667 map_lines += [indent*' ' + ''.join(line)]
668 indent = 0 if indent else 1
670 for line in map_lines_split:
671 map_lines += [''.join(line)]
672 window_center = YX(int(self.size.y / 2),
673 int(self.window_width / 2))
674 player = self.game.get_thing(self.game.player_id)
675 center = player.position
676 if self.mode.shows_info:
677 center = self.explorer
678 center = YX(center.y, center.x * 2)
679 offset = center - window_center
680 if type(self.game.map_geometry) == MapGeometryHex and offset.y % 2:
682 term_y = max(0, -offset.y)
683 term_x = max(0, -offset.x)
684 map_y = max(0, offset.y)
685 map_x = max(0, offset.x)
686 while (term_y < self.size.y and map_y < self.game.map_geometry.size.y):
687 to_draw = map_lines[map_y][map_x:self.window_width + offset.x]
688 safe_addstr(term_y, term_x, to_draw)
693 content = "%s help\n\n%s\n\n" % (self.mode.short_desc,
694 self.mode.help_intro)
695 if self.mode.name == 'play':
696 content += "Available actions:\n"
697 if 'MOVE' in self.game.tasks:
698 content += "[%s] – move player\n" % ','.join(self.movement_keys)
699 if 'PICK_UP' in self.game.tasks:
700 content += "[%s] – take thing under player\n" % self.keys['take_thing']
701 if 'DROP' in self.game.tasks:
702 content += "[%s] – drop carried thing\n" % self.keys['drop_thing']
703 if 'FLATTEN_SURROUNDINGS' in self.game.tasks:
704 content += "[%s] – flatten player's surroundings\n" % self.keys['flatten']
705 content += '[%s] – teleport to other space\n' % self.keys['teleport']
707 elif self.mode.name == 'study':
708 content += 'Available actions:\n'
709 content += '[%s] – move question mark\n' % ','.join(self.movement_keys)
710 content += '[%s] – toggle view between terrain, annotations, and password protection areas\n' % self.keys['toggle_map_mode']
712 elif self.mode.name == 'chat':
713 content += '/nick NAME – re-name yourself to NAME\n'
714 content += '/%s or /play – switch to play mode\n' % self.keys['switch_to_play']
715 content += '/%s or /study – switch to study mode\n' % self.keys['switch_to_study']
716 content += self.mode.list_available_modes(self)
717 for i in range(self.size.y):
719 self.window_width * (not self.mode.has_input_prompt),
720 ' '*self.window_width)
722 for line in content.split('\n'):
723 lines += msg_into_lines_of_width(line, self.window_width)
724 for i in range(len(lines)):
728 self.window_width * (not self.mode.has_input_prompt),
733 if self.mode.has_input_prompt:
736 if self.mode.shows_info:
741 if not self.mode.is_intro:
747 curses.curs_set(False) # hide cursor
748 curses.use_default_colors();
751 self.explorer = YX(0, 0)
754 interval = datetime.timedelta(seconds=5)
755 last_ping = datetime.datetime.now() - interval
757 if self.disconnected and self.force_instant_connect:
758 self.force_instant_connect = False
760 now = datetime.datetime.now()
761 if now - last_ping > interval:
762 if self.disconnected:
772 self.do_refresh = False
775 msg = self.queue.get(block=False)
780 key = stdscr.getkey()
781 self.do_refresh = True
784 self.show_help = False
785 if key == 'KEY_RESIZE':
787 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
788 self.input_ = self.input_[:-1]
789 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
790 self.show_help = True
792 self.restore_input_values()
793 elif self.mode.has_input_prompt and key != '\n': # Return key
795 max_length = self.window_width * self.size.y - len(input_prompt) - 1
796 if len(self.input_) > max_length:
797 self.input_ = self.input_[:max_length]
798 elif key == self.keys['help'] and not self.mode.is_single_char_entry:
799 self.show_help = True
800 elif self.mode.name == 'login' and key == '\n':
801 self.login_name = self.input_
802 self.send('LOGIN ' + quote(self.input_))
804 elif self.mode.name == 'control_pw_pw' and key == '\n':
805 if self.input_ == '':
806 self.log_msg('@ aborted')
808 self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
810 self.switch_mode('play')
811 elif self.mode.name == 'password' and key == '\n':
812 if self.input_ == '':
814 self.password = self.input_
816 self.switch_mode('play')
817 elif self.mode.name == 'admin' and key == '\n':
818 self.send('BECOME_ADMIN ' + quote(self.input_))
820 self.switch_mode('play')
821 elif self.mode.name == 'chat' and key == '\n':
822 if self.input_ == '':
824 if self.input_[0] == '/': # FIXME fails on empty input
825 if self.input_ in {'/' + self.keys['switch_to_play'], '/play'}:
826 self.switch_mode('play')
827 elif self.input_ in {'/' + self.keys['switch_to_study'], '/study'}:
828 self.switch_mode('study')
829 elif self.input_.startswith('/nick'):
830 tokens = self.input_.split(maxsplit=1)
832 self.send('NICK ' + quote(tokens[1]))
834 self.log_msg('? need login name')
836 self.log_msg('? unknown command')
838 self.send('ALL ' + quote(self.input_))
840 elif self.mode.name == 'annotate' and key == '\n':
841 if self.input_ == '':
843 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
844 quote(self.password)))
846 self.switch_mode('play')
847 elif self.mode.name == 'portal' and key == '\n':
848 if self.input_ == '':
850 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
851 quote(self.password)))
853 self.switch_mode('play')
854 elif self.mode.name == 'study':
855 if self.mode.mode_switch_on_key(self, key):
857 elif key == self.keys['toggle_map_mode']:
858 if self.map_mode == 'terrain':
859 self.map_mode = 'annotations'
860 elif self.map_mode == 'annotations':
861 self.map_mode = 'control'
863 self.map_mode = 'terrain'
864 elif key in self.movement_keys:
865 move_explorer(self.movement_keys[key])
866 elif self.mode.name == 'play':
867 if self.mode.mode_switch_on_key(self, key):
869 if key == self.keys['flatten'] and\
870 'FLATTEN_SURROUNDINGS' in self.game.tasks:
871 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
872 elif key == self.keys['take_thing'] and 'PICK_UP' in self.game.tasks:
873 self.send('TASK:PICK_UP')
874 elif key == self.keys['drop_thing'] and 'DROP' in self.game.tasks:
875 self.send('TASK:DROP')
876 elif key == self.keys['teleport']:
877 player = self.game.get_thing(self.game.player_id)
878 if player.position in self.game.portals:
879 self.host = self.game.portals[player.position]
883 self.log_msg('? not standing on portal')
884 elif key in self.movement_keys and 'MOVE' in self.game.tasks:
885 self.send('TASK:MOVE ' + self.movement_keys[key])
886 elif self.mode.name == 'edit':
887 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
888 self.switch_mode('play')
889 elif self.mode.name == 'control_pw_type':
890 self.tile_control_char = key
891 self.switch_mode('control_pw_pw')
892 elif self.mode.name == 'control_tile_type':
893 self.tile_control_char = key
894 self.switch_mode('control_tile_draw')
895 elif self.mode.name == 'control_tile_draw':
896 if self.mode.mode_switch_on_key(self, key):
898 elif key in self.movement_keys:
899 move_explorer(self.movement_keys[key])
901 #TUI('localhost:5000')
902 TUI('wss://plomlompom.com/rogue_chat/')