4 from plomrogue.game import GameBase
5 from plomrogue.parser import Parser
6 from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex
7 from plomrogue.things import ThingBase
8 from plomrogue.misc import quote
9 from plomrogue.errors import ArgError
10 from plomrogue_client.socket import ClientSocket
11 from plomrogue_client.tui import msg_into_lines_of_width, TUI
19 'long': 'This mode allows you to interact with the map in various ways.'
24 '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. Toggle the map view to show or hide different information layers.'},
26 'short': 'world edit',
28 'long': 'This mode allows you to change the game world in various ways. Individual map tiles can be protected by "protection characters", which you can see by toggling into the protections map view. You can edit a tile if you set the world edit password that matches its protection character. The character "." marks the absence of protection: Such tiles can always be edited.'
31 'short': 'name thing',
33 'long': 'Give name to/change name of carried thing.'
38 'long': 'Enter a command to the thing you carry. Enter nothing to return to play mode.'
42 'intro': 'Pick up a thing in reach by entering its index number. Enter nothing to abort.',
43 'long': 'You see a list of things which you could pick up. Enter the target thing\'s index, or, to leave, nothing.'
47 'intro': 'Enter number of direction to which you want to drop thing.',
48 'long': 'Drop currently carried thing by entering the target direction index. Enter nothing to return to play mode..'
50 'admin_thing_protect': {
51 'short': 'change thing protection',
52 'intro': '@ enter thing protection character:',
53 'long': 'Change protection character for carried thing.'
57 'intro': '@ enter face line:',
58 'long': 'Draw your face as ASCII art. The string you enter must be 18 characters long, and will be divided on display into 3 lines of 6 characters each, from top to bottom..'
61 'short': 'edit design',
62 'intro': '@ enter design:',
63 'long': 'Enter design for carried thing as ASCII art.'
68 'long': 'This mode allows you to change the map tile you currently stand on (if your world editing password authorizes you so). Just enter any printable ASCII character to imprint it on the ground below you.'
71 'short': 'change protection character password',
72 'intro': '@ enter protection character for which you want to change the password:',
73 'long': 'This mode is the first of two steps to change the password for a protection character. First enter the protection character for which you want to change the password.'
76 'short': 'change protection character password',
78 'long': 'This mode is the second of two steps to change the password for a protection character. Enter the new password for the protection character you chose.'
80 'control_tile_type': {
81 'short': 'change tiles protection',
82 'intro': '@ enter protection character which you want to draw:',
83 'long': 'This mode is the first of two steps to change tile protection areas on the map. First enter the tile protection character you want to write.'
85 'control_tile_draw': {
86 'short': 'change tiles protection',
88 'long': 'This mode is the second of two steps to change tile protection areas on the map. Toggle tile protection drawing on/off and move the ?? cursor around the map to draw the selected protection character.'
91 'short': 'annotate tile',
93 'long': 'This mode allows you to add/edit a comment on the tile you are currently standing on (provided your world editing password authorizes you so). Hit Return to leave.'
96 'short': 'edit portal',
98 'long': 'This mode allows you to imprint/edit/remove a teleportation target on the ground you are currently standing on (provided your world 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.'
103 '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:\n\n/nick NAME – re-name yourself to NAME'
108 'long': 'Enter your player name.'
110 'waiting_for_server': {
111 'short': 'waiting for server response',
112 'intro': '@ waiting for server …',
113 'long': 'Waiting for a server response.'
116 'short': 'waiting for server response',
118 'long': 'Waiting for a server response.'
121 'short': 'set world edit password',
123 'long': 'This mode allows you to change the password that you send to authorize yourself for editing password-protected elements of the world. Hit return to confirm and leave.'
126 'short': 'become admin',
127 'intro': '@ enter admin password:',
128 'long': 'This mode allows you to become admin if you know an admin password.'
133 'long': 'This mode allows you access to actions limited to administrators.'
137 def cmd_TURN(game, n):
138 game.turn_complete = False
139 cmd_TURN.argtypes = 'int:nonneg'
141 def cmd_OTHER_WIPE(game):
142 game.portals_new = {}
143 game.annotations_new = {}
145 cmd_OTHER_WIPE.argtypes = ''
147 def cmd_LOGIN_OK(game):
148 game.tui.switch_mode('post_login_wait')
149 game.tui.send('GET_GAMESTATE')
150 game.tui.log_msg('@ welcome!')
151 cmd_LOGIN_OK.argtypes = ''
153 def cmd_ADMIN_OK(game):
154 game.tui.is_admin = True
155 game.tui.log_msg('@ you now have admin rights')
156 game.tui.switch_mode('admin')
157 game.tui.do_refresh = True
158 cmd_ADMIN_OK.argtypes = ''
160 def cmd_REPLY(game, msg):
161 game.tui.log_msg('#MUSICPLAYER: ' + msg)
162 game.tui.do_refresh = True
163 cmd_REPLY.argtypes = 'string'
165 def cmd_CHAT(game, msg):
166 game.tui.log_msg('# ' + msg)
167 game.tui.do_refresh = True
168 cmd_CHAT.argtypes = 'string'
170 def cmd_CHATFACE(game, thing_id):
171 game.tui.draw_face = thing_id
172 game.tui.do_refresh = True
173 cmd_CHATFACE.argtypes = 'int:pos'
175 def cmd_PLAYER_ID(game, player_id):
176 game.player_id = player_id
177 cmd_PLAYER_ID.argtypes = 'int:nonneg'
179 def cmd_PLAYERS_HAT_CHARS(game, hat_chars):
180 game.players_hat_chars_new = hat_chars
181 cmd_PLAYERS_HAT_CHARS.argtypes = 'string'
183 def cmd_THING(game, yx, thing_type, protection, thing_id, portable, commandable):
184 t = game.get_thing_temp(thing_id)
186 t = ThingBase(game, thing_id)
187 game.things_new += [t]
190 t.protection = protection
191 t.portable = portable
192 t.commandable = commandable
193 cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type char int:nonneg bool bool'
195 def cmd_THING_NAME(game, thing_id, name):
196 t = game.get_thing_temp(thing_id)
198 cmd_THING_NAME.argtypes = 'int:pos string'
200 def cmd_THING_FACE(game, thing_id, face):
201 t = game.get_thing_temp(thing_id)
203 cmd_THING_FACE.argtypes = 'int:pos string'
205 def cmd_THING_HAT(game, thing_id, hat):
206 t = game.get_thing_temp(thing_id)
208 cmd_THING_HAT.argtypes = 'int:pos string'
210 def cmd_THING_DESIGN(game, thing_id, size, design):
211 t = game.get_thing_temp(thing_id)
212 t.design = [size, design]
213 cmd_THING_DESIGN.argtypes = 'int:pos yx_tuple string'
215 def cmd_THING_CHAR(game, thing_id, c):
216 t = game.get_thing_temp(thing_id)
218 cmd_THING_CHAR.argtypes = 'int:pos char'
220 def cmd_MAP(game, geometry, size, content):
221 map_geometry_class = globals()['MapGeometry' + geometry]
222 game.map_geometry_new = map_geometry_class(size)
223 game.map_content_new = content
224 if type(game.map_geometry_new) == MapGeometrySquare:
225 game.tui.movement_keys = {
226 game.tui.keys['square_move_up']: 'UP',
227 game.tui.keys['square_move_left']: 'LEFT',
228 game.tui.keys['square_move_down']: 'DOWN',
229 game.tui.keys['square_move_right']: 'RIGHT',
231 elif type(game.map_geometry_new) == MapGeometryHex:
232 game.tui.movement_keys = {
233 game.tui.keys['hex_move_upleft']: 'UPLEFT',
234 game.tui.keys['hex_move_upright']: 'UPRIGHT',
235 game.tui.keys['hex_move_right']: 'RIGHT',
236 game.tui.keys['hex_move_downright']: 'DOWNRIGHT',
237 game.tui.keys['hex_move_downleft']: 'DOWNLEFT',
238 game.tui.keys['hex_move_left']: 'LEFT',
240 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
242 def cmd_FOV(game, content):
243 game.fov_new = content
244 cmd_FOV.argtypes = 'string'
246 def cmd_MAP_CONTROL(game, content):
247 game.map_control_content_new = content
248 cmd_MAP_CONTROL.argtypes = 'string'
250 def cmd_GAME_STATE_COMPLETE(game):
251 game.tui.do_refresh = True
252 game.tui.info_cached = None
253 game.things = game.things_new
254 game.portals = game.portals_new
255 game.annotations = game.annotations_new
256 game.fov = game.fov_new
257 game.map_geometry = game.map_geometry_new
258 game.map_content = game.map_content_new
259 game.map_control_content = game.map_control_content_new
260 game.player = game.get_thing(game.player_id)
261 game.players_hat_chars = game.players_hat_chars_new
262 game.bladder_pressure = game.bladder_pressure_new
263 game.energy = game.energy_new
264 game.turn_complete = True
265 if game.tui.mode.name == 'post_login_wait':
266 game.tui.switch_mode('play')
267 cmd_GAME_STATE_COMPLETE.argtypes = ''
269 def cmd_PORTAL(game, position, msg):
270 game.portals_new[position] = msg
271 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
273 def cmd_PLAY_ERROR(game, msg):
274 game.tui.log_msg('? ' + msg)
275 game.tui.flash = True
276 game.tui.do_refresh = True
277 cmd_PLAY_ERROR.argtypes = 'string'
279 def cmd_GAME_ERROR(game, msg):
280 game.tui.log_msg('? game error: ' + msg)
281 game.tui.do_refresh = True
282 cmd_GAME_ERROR.argtypes = 'string'
284 def cmd_ARGUMENT_ERROR(game, msg):
285 game.tui.log_msg('? syntax error: ' + msg)
286 game.tui.do_refresh = True
287 cmd_ARGUMENT_ERROR.argtypes = 'string'
289 def cmd_ANNOTATION(game, position, msg):
290 game.annotations_new[position] = msg
291 if game.tui.mode.shows_info:
292 game.tui.do_refresh = True
293 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
295 def cmd_TASKS(game, tasks_comma_separated):
296 game.tasks = tasks_comma_separated.split(',')
297 game.tui.mode_write.legal = 'WRITE' in game.tasks
298 game.tui.mode_command_thing.legal = 'COMMAND' in game.tasks
299 game.tui.mode_take_thing.legal = 'PICK_UP' in game.tasks
300 game.tui.mode_drop_thing.legal = 'DROP' in game.tasks
301 cmd_TASKS.argtypes = 'string'
303 def cmd_THING_TYPE(game, thing_type, symbol_hint):
304 game.thing_types[thing_type] = symbol_hint
305 cmd_THING_TYPE.argtypes = 'string char'
307 def cmd_THING_INSTALLED(game, thing_id):
308 game.get_thing_temp(thing_id).installed = True
309 cmd_THING_INSTALLED.argtypes = 'int:pos'
311 def cmd_THING_CARRYING(game, thing_id, carried_id):
312 game.get_thing_temp(thing_id).carrying = game.get_thing_temp(carried_id)
313 cmd_THING_CARRYING.argtypes = 'int:pos int:pos'
315 def cmd_TERRAIN(game, terrain_char, terrain_desc):
316 game.terrains[terrain_char] = terrain_desc
317 cmd_TERRAIN.argtypes = 'char string'
321 cmd_PONG.argtypes = ''
323 def cmd_DEFAULT_COLORS(game):
324 game.tui.set_default_colors()
325 cmd_DEFAULT_COLORS.argtypes = ''
327 def cmd_RANDOM_COLORS(game):
328 game.tui.set_random_colors()
329 cmd_RANDOM_COLORS.argtypes = ''
331 def cmd_STATS(game, bladder_pressure, energy):
332 game.bladder_pressure_new = bladder_pressure
333 game.energy_new = energy
334 cmd_STATS.argtypes = 'int:nonneg int'
336 class Game(GameBase):
337 turn_complete = False
342 def __init__(self, *args, **kwargs):
343 super().__init__(*args, **kwargs)
344 self.register_command(cmd_LOGIN_OK)
345 self.register_command(cmd_ADMIN_OK)
346 self.register_command(cmd_PONG)
347 self.register_command(cmd_CHAT)
348 self.register_command(cmd_CHATFACE)
349 self.register_command(cmd_REPLY)
350 self.register_command(cmd_PLAYER_ID)
351 self.register_command(cmd_TURN)
352 self.register_command(cmd_OTHER_WIPE)
353 self.register_command(cmd_THING)
354 self.register_command(cmd_THING_TYPE)
355 self.register_command(cmd_THING_NAME)
356 self.register_command(cmd_THING_CHAR)
357 self.register_command(cmd_THING_FACE)
358 self.register_command(cmd_THING_HAT)
359 self.register_command(cmd_THING_DESIGN)
360 self.register_command(cmd_THING_CARRYING)
361 self.register_command(cmd_THING_INSTALLED)
362 self.register_command(cmd_TERRAIN)
363 self.register_command(cmd_MAP)
364 self.register_command(cmd_MAP_CONTROL)
365 self.register_command(cmd_PORTAL)
366 self.register_command(cmd_ANNOTATION)
367 self.register_command(cmd_GAME_STATE_COMPLETE)
368 self.register_command(cmd_PLAYERS_HAT_CHARS)
369 self.register_command(cmd_ARGUMENT_ERROR)
370 self.register_command(cmd_GAME_ERROR)
371 self.register_command(cmd_PLAY_ERROR)
372 self.register_command(cmd_TASKS)
373 self.register_command(cmd_FOV)
374 self.register_command(cmd_DEFAULT_COLORS)
375 self.register_command(cmd_RANDOM_COLORS)
376 self.register_command(cmd_STATS)
377 self.map_content = ''
378 self.players_hat_chars = ''
380 self.annotations = {}
381 self.annotations_new = {}
383 self.portals_new = {}
387 def get_string_options(self, string_option_type):
388 if string_option_type == 'map_geometry':
389 return ['Hex', 'Square']
390 elif string_option_type == 'thing_type':
391 return self.thing_types.keys()
394 def get_command(self, command_name):
395 from functools import partial
396 f = partial(self.commands[command_name], self)
397 f.argtypes = self.commands[command_name].argtypes
400 def get_thing_temp(self, id_):
401 for thing in self.things_new:
408 def __init__(self, name, has_input_prompt=False, shows_info=False,
409 is_intro=False, is_single_char_entry=False):
411 self.short_desc = mode_helps[name]['short']
412 self.available_modes = []
413 self.available_actions = []
414 self.has_input_prompt = has_input_prompt
415 self.shows_info = shows_info
416 self.is_intro = is_intro
417 self.help_intro = mode_helps[name]['long']
418 self.intro_msg = mode_helps[name]['intro']
419 self.is_single_char_entry = is_single_char_entry
422 def iter_available_modes(self, tui):
423 for mode_name in self.available_modes:
424 mode = getattr(tui, 'mode_' + mode_name)
427 key = tui.keys['switch_to_' + mode.name]
430 def list_available_modes(self, tui):
432 if len(self.available_modes) > 0:
433 msg = 'Other modes available from here:\n'
434 for mode, key in self.iter_available_modes(tui):
435 msg += '[%s] – %s\n' % (key, mode.short_desc)
438 def mode_switch_on_key(self, tui, key_pressed):
439 for mode, key in self.iter_available_modes(tui):
440 if key_pressed == key:
441 tui.switch_mode(mode.name)
445 class RogueChatTUI(TUI):
446 mode_admin_enter = Mode('admin_enter', has_input_prompt=True)
447 mode_admin = Mode('admin')
448 mode_play = Mode('play')
449 mode_study = Mode('study', shows_info=True)
450 mode_write = Mode('write', is_single_char_entry=True)
451 mode_edit = Mode('edit')
452 mode_control_pw_type = Mode('control_pw_type', has_input_prompt=True)
453 mode_control_pw_pw = Mode('control_pw_pw', has_input_prompt=True)
454 mode_control_tile_type = Mode('control_tile_type', has_input_prompt=True)
455 mode_control_tile_draw = Mode('control_tile_draw')
456 mode_admin_thing_protect = Mode('admin_thing_protect', has_input_prompt=True)
457 mode_annotate = Mode('annotate', has_input_prompt=True, shows_info=True)
458 mode_portal = Mode('portal', has_input_prompt=True, shows_info=True)
459 mode_chat = Mode('chat', has_input_prompt=True)
460 mode_waiting_for_server = Mode('waiting_for_server', is_intro=True)
461 mode_login = Mode('login', has_input_prompt=True, is_intro=True)
462 mode_post_login_wait = Mode('post_login_wait', is_intro=True)
463 mode_password = Mode('password', has_input_prompt=True)
464 mode_name_thing = Mode('name_thing', has_input_prompt=True, shows_info=True)
465 mode_command_thing = Mode('command_thing', has_input_prompt=True)
466 mode_take_thing = Mode('take_thing', has_input_prompt=True)
467 mode_drop_thing = Mode('drop_thing', has_input_prompt=True)
468 mode_enter_face = Mode('enter_face', has_input_prompt=True)
469 mode_enter_design = Mode('enter_design', has_input_prompt=True)
473 def __init__(self, host, *args, **kwargs):
476 self.mode_play.available_modes = ["chat", "study", "edit", "admin_enter",
477 "command_thing", "take_thing",
479 self.mode_play.available_actions = ["move", "teleport", "door", "consume",
480 "install", "wear", "spin", "dance"]
481 self.mode_study.available_modes = ["chat", "play", "admin_enter", "edit"]
482 self.mode_study.available_actions = ["toggle_map_mode", "move_explorer"]
483 self.mode_admin.available_modes = ["admin_thing_protect", "control_pw_type",
484 "control_tile_type", "chat",
485 "study", "play", "edit"]
486 self.mode_admin.available_actions = ["move", "toggle_map_mode"]
487 self.mode_control_tile_draw.available_modes = ["admin_enter"]
488 self.mode_control_tile_draw.available_actions = ["move_explorer",
490 self.mode_edit.available_modes = ["write", "annotate", "portal",
491 "name_thing", "enter_face", "enter_design",
493 "chat", "study", "play", "admin_enter"]
494 self.mode_edit.available_actions = ["move", "flatten", "install",
497 self.socket = ClientSocket(host, self.socket_log)
500 self.parser = Parser(self.game)
501 self.login_name = None
502 self.map_mode = 'terrain + things'
503 self.password = 'foo'
505 'switch_to_chat': 't',
506 'switch_to_play': 'p',
507 'switch_to_password': 'P',
508 'switch_to_annotate': 'M',
509 'switch_to_portal': 'T',
510 'switch_to_study': '?',
511 'switch_to_edit': 'E',
512 'switch_to_write': 'm',
513 'switch_to_name_thing': 'N',
514 'switch_to_command_thing': 'O',
515 'switch_to_admin_enter': 'A',
516 'switch_to_control_pw_type': 'C',
517 'switch_to_control_tile_type': 'Q',
518 'switch_to_admin_thing_protect': 'T',
520 'switch_to_enter_face': 'f',
521 'switch_to_enter_design': 'D',
522 'switch_to_take_thing': 'z',
523 'switch_to_drop_thing': 'u',
532 'toggle_map_mode': 'L',
533 'toggle_tile_draw': 'm',
534 'hex_move_upleft': 'w',
535 'hex_move_upright': 'e',
536 'hex_move_right': 'd',
537 'hex_move_downright': 'x',
538 'hex_move_downleft': 'y',
539 'hex_move_left': 'a',
540 'square_move_up': 'w',
541 'square_move_left': 'a',
542 'square_move_down': 's',
543 'square_move_right': 'd',
545 if os.path.isfile('config.json'):
546 with open('config.json', 'r') as f:
547 keys_conf = json.loads(f.read())
549 self.keys[k] = keys_conf[k]
550 self.show_help = False
551 self.input_lines = []
555 self.ascii_draw_stage = 0
556 self.full_ascii_draw = ''
557 self.offset = YX(0,0)
558 self.explorer = YX(0, 0)
560 self.input_prompt = '> '
561 self.action_descriptions = {
563 'flatten': 'flatten surroundings',
564 'teleport': 'teleport',
565 'take_thing': 'pick up thing',
566 'drop_thing': 'drop thing',
567 'toggle_map_mode': 'toggle map view',
568 'toggle_tile_draw': 'toggle protection character drawing',
569 'install': '(un-)install',
571 'door': 'open/close',
572 'consume': 'consume',
576 self.action_tasks = {
577 'flatten': 'FLATTEN_SURROUNDINGS',
578 'take_thing': 'PICK_UP',
579 'drop_thing': 'DROP',
581 'install': 'INSTALL',
584 'command': 'COMMAND',
585 'consume': 'INTOXICATE',
589 super().__init__(*args, **kwargs)
591 def update_on_connect(self):
592 self.socket.send('TASKS')
593 self.socket.send('TERRAINS')
594 self.socket.send('THING_TYPES')
595 self.switch_mode('login')
599 self.log('@ attempting reconnect')
600 self.socket.send('QUIT')
601 # necessitated by some strange SSL race conditions with ws4py
602 time.sleep(0.1) # FIXME find out why exactly necessary
603 self.switch_mode('waiting_for_server')
604 self.socket.connect()
605 self.update_on_connect()
608 self.socket.send(msg)
609 if self.socket.disconnected:
610 self.do_refresh = True
612 def socket_log(self, msg):
615 def log_msg(self, msg):
618 if len(self._log) > 100:
619 self.log = self._log[-100:]
621 def restore_input_values(self):
622 if self.mode.name == 'annotate' and self.explorer in self.game.annotations:
623 self.input_ = self.game.annotations[self.explorer]
624 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
625 self.input_ = self.game.portals[self.explorer]
626 elif self.mode.name == 'password':
627 self.input_ = self.password
628 elif self.mode.name == 'name_thing':
629 if hasattr(self.game.player.carrying, 'name'):
630 self.input_ = self.game.player.carrying.name
631 elif self.mode.name == 'admin_thing_protect':
632 if hasattr(self.game.player.carrying, 'protection'):
633 self.input_ = self.game.player.carrying.protection
634 elif self.mode.name == 'enter_face':
635 start = self.ascii_draw_stage * 6
636 end = (self.ascii_draw_stage + 1) * 6
637 self.input_ = self.game.player.face[start:end]
638 elif self.mode.name == 'enter_design':
639 width = self.game.player.carrying.design[0].x
640 start = self.ascii_draw_stage * width
641 end = (self.ascii_draw_stage + 1) * width
642 self.input_ = self.game.player.carrying.design[1][start:end]
644 def send_tile_control_command(self):
645 self.send('SET_TILE_CONTROL %s %s' %
646 (self.explorer, quote(self.tile_control_char)))
648 def toggle_map_mode(self):
649 if self.map_mode == 'terrain only':
650 self.map_mode = 'terrain + annotations'
651 elif self.map_mode == 'terrain + annotations':
652 self.map_mode = 'terrain + things'
653 elif self.map_mode == 'terrain + things':
654 self.map_mode = 'protections'
655 elif self.map_mode == 'protections':
656 self.map_mode = 'terrain only'
658 def switch_mode(self, mode_name):
660 def fail(msg, return_mode='play'):
663 self.switch_mode(return_mode)
665 if self.mode and self.mode.name == 'control_tile_draw':
666 self.log('@ finished tile protection drawing.')
667 self.draw_face = False
668 self.tile_draw = False
669 self.ascii_draw_stage = 0
670 self.full_ascii_draw = ''
671 if mode_name == 'command_thing' and\
672 (not self.game.player.carrying or
673 not self.game.player.carrying.commandable):
674 return fail('not carrying anything commandable')
675 if mode_name == 'name_thing' and not self.game.player.carrying:
676 return fail('not carrying anything to re-name', 'edit')
677 if mode_name == 'admin_thing_protect' and not self.game.player.carrying:
678 return fail('not carrying anything to protect')
679 if mode_name == 'take_thing' and self.game.player.carrying:
680 return fail('already carrying something')
681 if mode_name == 'drop_thing' and not self.game.player.carrying:
682 return fail('not carrying anything droppable')
683 if mode_name == 'enter_design' and\
684 (not self.game.player.carrying or
685 not hasattr(self.game.player.carrying, 'design')):
686 return fail('not carrying designable to edit', 'edit')
687 if mode_name == 'admin_enter' and self.is_admin:
689 self.mode = getattr(self, 'mode_' + mode_name)
690 if self.mode.name in {'control_tile_draw', 'control_tile_type',
692 self.map_mode = 'protections'
693 elif self.mode.name != 'edit':
694 self.map_mode = 'terrain + things'
695 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
696 self.explorer = YX(self.game.player.position.y,
697 self.game.player.position.x)
698 if self.mode.is_single_char_entry:
699 self.show_help = True
700 if len(self.mode.intro_msg) > 0:
701 self.log(self.mode.intro_msg)
702 if self.mode.name == 'login':
704 self.send('LOGIN ' + quote(self.login_name))
706 self.log('@ enter username')
707 elif self.mode.name == 'take_thing':
708 self.log('Portable things in reach for pick-up:')
710 'HERE': YX(0, 0), 'LEFT': YX(0, -1), 'RIGHT': YX(0, 1)
712 if type(self.game.map_geometry) == MapGeometrySquare:
713 directed_moves['UP'] = YX(-1, 0)
714 directed_moves['DOWN'] = YX(1, 0)
715 elif type(self.game.map_geometry) == MapGeometryHex:
716 if self.game.player.position.y % 2:
717 directed_moves['UPLEFT'] = YX(-1, 0)
718 directed_moves['UPRIGHT'] = YX(-1, 1)
719 directed_moves['DOWNLEFT'] = YX(1, 0)
720 directed_moves['DOWNRIGHT'] = YX(1, 1)
722 directed_moves['UPLEFT'] = YX(-1, -1)
723 directed_moves['UPRIGHT'] = YX(-1, 0)
724 directed_moves['DOWNLEFT'] = YX(1, -1)
725 directed_moves['DOWNRIGHT'] = YX(1, 0)
727 for direction in directed_moves:
728 move = directed_moves[direction]
729 select_range[direction] = self.game.player.position + move
730 self.selectables = []
732 for direction in select_range:
733 for t in [t for t in self.game.things
734 if t.portable and t.position == select_range[direction]]:
735 self.selectables += [t.id_]
736 directions += [direction]
737 if len(self.selectables) == 0:
738 return fail('nothing to pick-up')
740 for i in range(len(self.selectables)):
741 t = self.game.get_thing(self.selectables[i])
742 self.log('%s %s: %s' % (i, directions[i],
743 self.get_thing_info(t)))
744 elif self.mode.name == 'drop_thing':
745 self.log('Direction to drop thing to:')
747 ['HERE'] + list(self.game.tui.movement_keys.values())
748 for i in range(len(self.selectables)):
749 self.log(str(i) + ': ' + self.selectables[i])
750 elif self.mode.name == 'enter_design':
751 if self.game.player.carrying.type_ == 'Hat':
752 self.log('@ The design you enter must be %s lines of max %s '
753 'characters width each'
754 % (self.game.player.carrying.design[0].y,
755 self.game.player.carrying.design[0].x))
756 self.log('@ Legal characters: ' + self.game.players_hat_chars)
757 self.log('@ (Eat cookies to extend the ASCII characters available for drawing.)')
759 self.log('@ Width of first line determines maximum width for remaining design')
760 self.log('@ Finish design by entering an empty line (multiple space characters do not count as empty)')
761 elif self.mode.name == 'command_thing':
762 self.send('TASK:COMMAND ' + quote('HELP'))
763 elif self.mode.name == 'control_pw_pw':
764 self.log('@ enter protection password for "%s":' % self.tile_control_char)
765 elif self.mode.name == 'control_tile_draw':
766 self.log('@ can draw protection character "%s", turn drawing on/off with [%s], finish with [%s].' % (self.tile_control_char, self.keys['toggle_tile_draw'], self.keys['switch_to_admin_enter']))
768 self.restore_input_values()
770 def set_default_colors(self):
771 if curses.can_change_color():
772 curses.init_color(7, 1000, 1000, 1000)
773 curses.init_color(0, 0, 0, 0)
774 self.do_refresh = True
776 def set_random_colors(self):
780 return int(offset + random.random()*375)
782 if curses.can_change_color():
783 curses.init_color(7, rand(625), rand(625), rand(625))
784 curses.init_color(0, rand(0), rand(0), rand(0))
785 self.do_refresh = True
789 return self.info_cached
790 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
792 if len(self.game.fov) > pos_i and self.game.fov[pos_i] != '.':
793 info_to_cache += 'outside field of view'
795 for t in self.game.things:
796 if t.position == self.explorer:
797 info_to_cache += '%s' % self.get_thing_info(t, True)
798 terrain_char = self.game.map_content[pos_i]
800 if terrain_char in self.game.terrains:
801 terrain_desc = self.game.terrains[terrain_char]
802 info_to_cache += 'TERRAIN: %s (%s' % (terrain_char,
804 protection = self.game.map_control_content[pos_i]
805 if protection != '.':
806 info_to_cache += '/protection:%s' % protection
807 info_to_cache += ')\n'
808 if self.explorer in self.game.portals:
809 info_to_cache += 'PORTAL: ' +\
810 self.game.portals[self.explorer] + '\n'
811 if self.explorer in self.game.annotations:
812 info_to_cache += 'ANNOTATION: ' +\
813 self.game.annotations[self.explorer]
814 self.info_cached = info_to_cache
815 return self.info_cached
817 def get_thing_info(self, t, detailed=False):
821 info += self.game.thing_types[t.type_]
822 if hasattr(t, 'thing_char'):
824 if hasattr(t, 'name'):
825 info += ': %s' % t.name
826 info += ' (%s' % t.type_
827 if hasattr(t, 'installed'):
829 if t.type_ == 'Bottle':
830 if t.thing_char == '_':
832 elif t.thing_char == '~':
835 protection = t.protection
836 if protection != '.':
837 info += '/protection:%s' % protection
839 if hasattr(t, 'hat') or hasattr(t, 'face'):
840 info += '----------\n'
841 if hasattr(t, 'hat'):
842 info += '| %s |\n' % t.hat[0:6]
843 info += '| %s |\n' % t.hat[6:12]
844 info += '| %s |\n' % t.hat[12:18]
845 if hasattr(t, 'face'):
846 info += '| %s |\n' % t.face[0:6]
847 info += '| %s |\n' % t.face[6:12]
848 info += '| %s |\n' % t.face[12:18]
849 info += '----------\n'
850 if hasattr(t, 'design'):
851 line_length = t.design[0].x
853 for i in range(t.design[0].y):
854 start = i * line_length
855 end = (i + 1) * line_length
856 lines += [t.design[1][start:end]]
857 info += '-' * (line_length + 4) + '\n'
859 info += '| %s |\n' % line
860 info += '-' * (line_length + 4) + '\n'
865 def reset_size(self):
867 self.left_window_width = min(52, int(self.size.x / 2))
868 self.right_window_width = self.size.x - self.left_window_width
870 def addstr(self, y, x, line, ignore=None):
871 super().addstr(y, x, line, curses.color_pair(1))
874 self.switch_mode('waiting_for_server')
876 self.set_default_colors()
877 curses.init_pair(1, 7, 0)
878 if not curses.can_change_color():
879 self.log('@ unfortunately, your terminal does not seem to '
880 'support re-definition of colors; you might miss out '
881 'on some color effects')
884 def recalc_input_lines(self):
885 if not self.mode.has_input_prompt:
886 self.input_lines = []
888 self.input_lines = msg_into_lines_of_width(self.input_prompt
890 self.right_window_width)
891 def draw_history(self):
893 for line in self._log:
894 lines += msg_into_lines_of_width(line, self.right_window_width)
897 max_y = self.size.y - len(self.input_lines)
898 for i in range(len(lines)):
899 if (i >= max_y - height_header):
901 self.addstr(max_y - i - 1, self.left_window_width, lines[i])
904 info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info())
905 lines = msg_into_lines_of_width(info, self.right_window_width)
907 for i in range(len(lines)):
908 y = height_header + i
909 if y >= self.size.y - len(self.input_lines):
911 self.addstr(y, self.left_window_width, lines[i])
913 def draw_input(self):
914 y = self.size.y - len(self.input_lines)
915 for i in range(len(self.input_lines)):
916 self.addstr(y, self.left_window_width, self.input_lines[i])
919 def draw_stats(self):
920 stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy,
921 self.game.bladder_pressure)
922 self.addstr(0, self.left_window_width, stats)
925 help = "hit [%s] for help" % self.keys['help']
926 if self.mode.has_input_prompt:
927 help = "enter /help for help"
928 self.addstr(1, self.left_window_width,
929 'MODE: %s – %s' % (self.mode.short_desc, help))
932 if (not self.game.turn_complete) and len(self.map_lines) == 0:
934 if self.game.turn_complete:
936 for y in range(self.game.map_geometry.size.y):
937 start = self.game.map_geometry.size.x * y
938 end = start + self.game.map_geometry.size.x
939 if self.map_mode == 'protections':
940 map_lines_split += [[c + ' ' for c
941 in self.game.map_control_content[start:end]]]
943 map_lines_split += [[c + ' ' for c
944 in self.game.map_content[start:end]]]
945 if self.map_mode == 'terrain + annotations':
946 for p in self.game.annotations:
947 map_lines_split[p.y][p.x] = 'A '
948 elif self.map_mode == 'terrain + things':
949 for p in self.game.portals.keys():
950 original = map_lines_split[p.y][p.x]
951 map_lines_split[p.y][p.x] = original[0] + 'P'
954 def draw_thing(t, used_positions):
955 symbol = self.game.thing_types[t.type_]
957 if hasattr(t, 'thing_char'):
958 meta_char = t.thing_char
959 if t.position in used_positions:
961 if hasattr(t, 'carrying') and t.carrying:
963 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
964 used_positions += [t.position]
966 for t in [t for t in self.game.things if t.type_ != 'Player']:
967 draw_thing(t, used_positions)
968 for t in [t for t in self.game.things if t.type_ == 'Player']:
969 draw_thing(t, used_positions)
970 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
971 map_lines_split[self.explorer.y][self.explorer.x] = '??'
972 elif self.map_mode != 'terrain + things':
973 map_lines_split[self.game.player.position.y]\
974 [self.game.player.position.x] = '??'
976 if type(self.game.map_geometry) == MapGeometryHex:
978 for line in map_lines_split:
979 self.map_lines += [indent * ' ' + ''.join(line)]
980 indent = 0 if indent else 1
982 for line in map_lines_split:
983 self.map_lines += [''.join(line)]
984 window_center = YX(int(self.size.y / 2),
985 int(self.left_window_width / 2))
986 center = self.game.player.position
987 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
988 center = self.explorer
989 center = YX(center.y, center.x * 2)
990 self.offset = center - window_center
991 if type(self.game.map_geometry) == MapGeometryHex and self.offset.y % 2:
992 self.offset += YX(0, 1)
993 term_y = max(0, -self.offset.y)
994 term_x = max(0, -self.offset.x)
995 map_y = max(0, self.offset.y)
996 map_x = max(0, self.offset.x)
997 while term_y < self.size.y and map_y < len(self.map_lines):
998 to_draw = self.map_lines[map_y][map_x:self.left_window_width + self.offset.x]
999 self.addstr(term_y, term_x, to_draw)
1003 def draw_names(self):
1004 players = [t for t in self.game.things if t.type_ == 'Player']
1005 players.sort(key=lambda t: len(t.name))
1007 shrink_offset = max(0, (self.size.y - self.left_window_width // 2) // 2)
1010 offset_y = y - shrink_offset
1011 max_len = max(5, (self.left_window_width // 2) - (offset_y * 2) - 8)
1013 if len(name) > max_len:
1014 name = name[:max_len - 1] + '…'
1015 self.addstr(y, 0, '@%s:%s' % (t.thing_char, name))
1017 if y >= self.size.y:
1020 def draw_face_popup(self):
1021 t = self.game.get_thing(self.draw_face)
1022 if not t or not hasattr(t, 'face'):
1023 self.draw_face = False
1026 start_x = self.left_window_width - 10
1027 def draw_body_part(body_part, end_y):
1028 self.addstr(end_y - 3, start_x, '----------')
1029 self.addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |')
1030 self.addstr(end_y - 1, start_x, '| ' + body_part[6:12] + ' |')
1031 self.addstr(end_y, start_x, '| ' + body_part[12:18] + ' |')
1033 if hasattr(t, 'face'):
1034 draw_body_part(t.face, self.size.y - 3)
1035 if hasattr(t, 'hat'):
1036 draw_body_part(t.hat, self.size.y - 6)
1037 self.addstr(self.size.y - 2, start_x, '----------')
1040 name = name[:6 - 1] + '…'
1041 self.addstr(self.size.y - 1, start_x, '@%s:%s' % (t.thing_char, name))
1043 def draw_help(self):
1044 content = "%s help\n\n%s\n\n" % (self.mode.short_desc,
1045 self.mode.help_intro)
1046 if len(self.mode.available_actions) > 0:
1047 content += "Available actions:\n"
1048 for action in self.mode.available_actions:
1049 if action in self.action_tasks:
1050 if self.action_tasks[action] not in self.game.tasks:
1052 if action == 'move_explorer':
1054 if action == 'move':
1055 key = ','.join(self.movement_keys)
1057 key = self.keys[action]
1058 content += '[%s] – %s\n' % (key, self.action_descriptions[action])
1060 content += self.mode.list_available_modes(self)
1061 for i in range(self.size.y):
1063 self.left_window_width * (not self.mode.has_input_prompt),
1064 ' ' * self.left_window_width)
1066 for line in content.split('\n'):
1067 lines += msg_into_lines_of_width(line, self.right_window_width)
1068 for i in range(len(lines)):
1069 if i >= self.size.y:
1072 self.left_window_width * (not self.mode.has_input_prompt),
1075 def draw_screen(self):
1076 super().draw_screen()
1077 self.stdscr.bkgd(' ', curses.color_pair(1))
1078 self.recalc_input_lines()
1079 if self.mode.has_input_prompt:
1081 if self.mode.shows_info:
1086 if not self.mode.is_intro:
1091 if self.mode.name in {'chat', 'play'}:
1094 self.draw_face_popup()
1096 def handle_server_message(self, msg):
1097 command, args = self.parser.parse(msg)
1100 def on_each_loop_start(self):
1101 prev_disconnected = self.socket.disconnected
1102 self.socket.keep_connection_alive()
1103 if prev_disconnected and not self.socket.disconnected:
1104 self.update_on_connect()
1109 def on_key(self, key, keycode):
1111 def task_action_on(action):
1112 return self.action_tasks[action] in self.game.tasks
1114 def move_explorer(direction):
1115 target = self.game.map_geometry.move_yx(self.explorer, direction)
1117 self.info_cached = None
1118 self.explorer = target
1120 self.send_tile_control_command()
1123 def pick_selectable(task_name):
1125 i = int(self.input_)
1126 if i < 0 or i >= len(self.selectables):
1127 self.log('? invalid index, aborted')
1129 self.send('TASK:%s %s' % (task_name, self.selectables[i]))
1131 self.log('? invalid index, aborted')
1133 self.switch_mode('play')
1135 def enter_ascii_art(command, height, width,
1136 with_pw=False, with_size=False):
1137 if with_size and self.ascii_draw_stage == 0:
1138 width = len(self.input_)
1140 self.log('? input too long, must be max 36; try again')
1141 # TODO: move max width mechanism server-side
1143 old_size = self.game.player.carrying.design[0]
1144 if width != old_size.x:
1145 # TODO: save remaining design?
1146 self.game.player.carrying.design[1] = ''
1147 self.game.player.carrying.design[0] = YX(old_size.y, width)
1148 elif len(self.input_) > width:
1149 self.log('? input too long, '
1150 'must be max %s; try again' % width)
1152 self.log(' ' + self.input_)
1153 if with_size and self.input_ in {'', ' '}\
1154 and self.ascii_draw_stage > 0:
1155 height = self.ascii_draw_stage
1158 height = self.ascii_draw_stage + 2
1159 if len(self.input_) < width:
1160 self.input_ += ' ' * (width - len(self.input_))
1161 self.full_ascii_draw += self.input_
1163 old_size = self.game.player.carrying.design[0]
1164 self.game.player.carrying.design[0] = YX(height, old_size.x)
1165 self.ascii_draw_stage += 1
1166 if self.ascii_draw_stage < height:
1167 self.restore_input_values()
1169 if with_pw and with_size:
1170 self.send('%s_SIZE %s %s' % (command, YX(height, width),
1171 quote(self.password)))
1173 self.send('%s %s %s' % (command, quote(self.full_ascii_draw),
1174 quote(self.password)))
1176 self.send('%s %s' % (command, quote(self.full_ascii_draw)))
1177 self.full_ascii_draw = ""
1178 self.ascii_draw_stage = 0
1180 self.switch_mode('edit')
1182 self.show_help = False
1183 self.draw_face = False
1184 if key == 'KEY_RESIZE':
1186 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
1187 self.input_ = self.input_[:-1]
1188 elif (((not self.mode.is_intro) and keycode == 27) # Escape
1189 or (self.mode.has_input_prompt and key == '\n'
1190 and self.input_ == ''\
1191 and self.mode.name in {'chat', 'command_thing',
1192 'take_thing', 'drop_thing',
1194 if self.mode.name not in {'chat', 'play', 'study', 'edit'}:
1195 self.log('@ aborted')
1196 self.switch_mode('play')
1197 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
1198 self.show_help = True
1200 self.restore_input_values()
1201 elif self.mode.has_input_prompt and key != '\n': # Return key
1203 max_length = self.right_window_width * self.size.y - len(self.input_prompt) - 1
1204 if len(self.input_) > max_length:
1205 self.input_ = self.input_[:max_length]
1206 elif key == self.keys['help'] and not self.mode.is_single_char_entry:
1207 self.show_help = True
1208 elif self.mode.name == 'login' and key == '\n':
1209 self.login_name = self.input_
1210 self.send('LOGIN ' + quote(self.input_))
1212 elif self.mode.name == 'enter_face' and key == '\n':
1213 enter_ascii_art('PLAYER_FACE', 3, 6)
1214 elif self.mode.name == 'enter_design' and key == '\n':
1215 if self.game.player.carrying.type_ == 'Hat':
1216 enter_ascii_art('THING_DESIGN',
1217 self.game.player.carrying.design[0].y,
1218 self.game.player.carrying.design[0].x, True)
1220 enter_ascii_art('THING_DESIGN',
1221 self.game.player.carrying.design[0].y,
1222 self.game.player.carrying.design[0].x,
1224 elif self.mode.name == 'take_thing' and key == '\n':
1225 pick_selectable('PICK_UP')
1226 elif self.mode.name == 'drop_thing' and key == '\n':
1227 pick_selectable('DROP')
1228 elif self.mode.name == 'command_thing' and key == '\n':
1229 self.send('TASK:COMMAND ' + quote(self.input_))
1231 elif self.mode.name == 'control_pw_pw' and key == '\n':
1232 if self.input_ == '':
1233 self.log('@ aborted')
1235 self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
1236 self.log('@ sent new password for protection character "%s"' % self.tile_control_char)
1237 self.switch_mode('admin')
1238 elif self.mode.name == 'password' and key == '\n':
1239 if self.input_ == '':
1241 self.password = self.input_
1242 self.switch_mode('edit')
1243 elif self.mode.name == 'admin_enter' and key == '\n':
1244 self.send('BECOME_ADMIN ' + quote(self.input_))
1245 self.switch_mode('play')
1246 elif self.mode.name == 'control_pw_type' and key == '\n':
1247 if len(self.input_) != 1:
1248 self.log('@ entered non-single-char, therefore aborted')
1249 self.switch_mode('admin')
1251 self.tile_control_char = self.input_
1252 self.switch_mode('control_pw_pw')
1253 elif self.mode.name == 'admin_thing_protect' and key == '\n':
1254 if len(self.input_) != 1:
1255 self.log('@ entered non-single-char, therefore aborted')
1257 self.send('THING_PROTECTION %s' % (quote(self.input_)))
1258 self.log('@ sent new protection character for thing')
1259 self.switch_mode('admin')
1260 elif self.mode.name == 'control_tile_type' and key == '\n':
1261 if len(self.input_) != 1:
1262 self.log('@ entered non-single-char, therefore aborted')
1263 self.switch_mode('admin')
1265 self.tile_control_char = self.input_
1266 self.switch_mode('control_tile_draw')
1267 elif self.mode.name == 'chat' and key == '\n':
1268 if self.input_ == '':
1270 if self.input_[0] == '/':
1271 if self.input_.startswith('/nick'):
1272 tokens = self.input_.split(maxsplit=1)
1273 if len(tokens) == 2:
1274 self.send('NICK ' + quote(tokens[1]))
1276 self.log('? need login name')
1278 self.log('? unknown command')
1280 self.send('ALL ' + quote(self.input_))
1282 elif self.mode.name == 'name_thing' and key == '\n':
1283 if self.input_ == '':
1285 self.send('THING_NAME %s %s' % (quote(self.input_),
1286 quote(self.password)))
1287 self.switch_mode('edit')
1288 elif self.mode.name == 'annotate' and key == '\n':
1289 if self.input_ == '':
1291 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
1292 quote(self.password)))
1293 self.switch_mode('edit')
1294 elif self.mode.name == 'portal' and key == '\n':
1295 if self.input_ == '':
1297 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
1298 quote(self.password)))
1299 self.switch_mode('edit')
1300 elif self.mode.name == 'study':
1301 if self.mode.mode_switch_on_key(self, key):
1303 elif key == self.keys['toggle_map_mode']:
1304 self.toggle_map_mode()
1305 elif key in self.movement_keys:
1306 move_explorer(self.movement_keys[key])
1307 elif self.mode.name == 'play':
1308 if self.mode.mode_switch_on_key(self, key):
1310 elif key == self.keys['door'] and task_action_on('door'):
1311 self.send('TASK:DOOR')
1312 elif key == self.keys['consume'] and task_action_on('consume'):
1313 self.send('TASK:INTOXICATE')
1314 elif key == self.keys['wear'] and task_action_on('wear'):
1315 self.send('TASK:WEAR')
1316 elif key == self.keys['spin'] and task_action_on('spin'):
1317 self.send('TASK:SPIN')
1318 elif key == self.keys['dance'] and task_action_on('dance'):
1319 self.send('TASK:DANCE')
1320 elif key == self.keys['teleport']:
1321 if self.game.player.position in self.game.portals:
1322 self.socket.host = self.game.portals[self.game.player.position]
1326 self.log('? not standing on portal')
1327 elif key in self.movement_keys and task_action_on('move'):
1328 self.send('TASK:MOVE ' + self.movement_keys[key])
1329 elif self.mode.name == 'write':
1330 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
1331 self.switch_mode('edit')
1332 elif self.mode.name == 'control_tile_draw':
1333 if self.mode.mode_switch_on_key(self, key):
1335 elif key in self.movement_keys:
1336 move_explorer(self.movement_keys[key])
1337 elif key == self.keys['toggle_tile_draw']:
1338 self.tile_draw = False if self.tile_draw else True
1339 elif self.mode.name == 'admin':
1340 if self.mode.mode_switch_on_key(self, key):
1342 elif key == self.keys['toggle_map_mode']:
1343 self.toggle_map_mode()
1344 elif key in self.movement_keys and task_action_on('move'):
1345 self.send('TASK:MOVE ' + self.movement_keys[key])
1346 elif self.mode.name == 'edit':
1347 if self.mode.mode_switch_on_key(self, key):
1349 elif key == self.keys['flatten'] and task_action_on('flatten'):
1350 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
1351 elif key == self.keys['install'] and task_action_on('install'):
1352 self.send('TASK:INSTALL %s' % quote(self.password))
1353 elif key == self.keys['toggle_map_mode']:
1354 self.toggle_map_mode()
1355 elif key in self.movement_keys and task_action_on('move'):
1356 self.send('TASK:MOVE ' + self.movement_keys[key])
1358 if len(sys.argv) != 2:
1359 raise ArgError('wrong number of arguments, need game host')