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.do_refresh = True
502 self.login_name = None
503 self.map_mode = 'terrain + things'
504 self.password = 'foo'
506 'switch_to_chat': 't',
507 'switch_to_play': 'p',
508 'switch_to_password': 'P',
509 'switch_to_annotate': 'M',
510 'switch_to_portal': 'T',
511 'switch_to_study': '?',
512 'switch_to_edit': 'E',
513 'switch_to_write': 'm',
514 'switch_to_name_thing': 'N',
515 'switch_to_command_thing': 'O',
516 'switch_to_admin_enter': 'A',
517 'switch_to_control_pw_type': 'C',
518 'switch_to_control_tile_type': 'Q',
519 'switch_to_admin_thing_protect': 'T',
521 'switch_to_enter_face': 'f',
522 'switch_to_enter_design': 'D',
523 'switch_to_take_thing': 'z',
524 'switch_to_drop_thing': 'u',
533 'toggle_map_mode': 'L',
534 'toggle_tile_draw': 'm',
535 'hex_move_upleft': 'w',
536 'hex_move_upright': 'e',
537 'hex_move_right': 'd',
538 'hex_move_downright': 'x',
539 'hex_move_downleft': 'y',
540 'hex_move_left': 'a',
541 'square_move_up': 'w',
542 'square_move_left': 'a',
543 'square_move_down': 's',
544 'square_move_right': 'd',
546 if os.path.isfile('config.json'):
547 with open('config.json', 'r') as f:
548 keys_conf = json.loads(f.read())
550 self.keys[k] = keys_conf[k]
551 self.show_help = False
552 self.input_lines = []
556 self.ascii_draw_stage = 0
557 self.full_ascii_draw = ''
558 self.offset = YX(0,0)
559 self.explorer = YX(0, 0)
561 self.store_widechar = False
562 self.input_prompt = '> '
563 self.action_descriptions = {
565 'flatten': 'flatten surroundings',
566 'teleport': 'teleport',
567 'take_thing': 'pick up thing',
568 'drop_thing': 'drop thing',
569 'toggle_map_mode': 'toggle map view',
570 'toggle_tile_draw': 'toggle protection character drawing',
571 'install': '(un-)install',
573 'door': 'open/close',
574 'consume': 'consume',
578 self.action_tasks = {
579 'flatten': 'FLATTEN_SURROUNDINGS',
580 'take_thing': 'PICK_UP',
581 'drop_thing': 'DROP',
583 'install': 'INSTALL',
586 'command': 'COMMAND',
587 'consume': 'INTOXICATE',
591 super().__init__(*args, **kwargs)
593 def update_on_connect(self):
594 self.socket.send('TASKS')
595 self.socket.send('TERRAINS')
596 self.socket.send('THING_TYPES')
597 self.switch_mode('login')
601 self.log('@ attempting reconnect')
602 self.socket.send('QUIT')
603 # necessitated by some strange SSL race conditions with ws4py
604 time.sleep(0.1) # FIXME find out why exactly necessary
605 self.switch_mode('waiting_for_server')
606 self.socket.connect()
607 self.update_on_connect()
610 self.socket.send(msg)
611 if self.socket.disconnected:
612 self.do_refresh = True
614 def socket_log(self, msg):
617 def log_msg(self, msg):
620 if len(self._log) > 100:
621 self.log = self._log[-100:]
623 def restore_input_values(self):
624 if self.mode.name == 'annotate' and self.explorer in self.game.annotations:
625 self.input_ = self.game.annotations[self.explorer]
626 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
627 self.input_ = self.game.portals[self.explorer]
628 elif self.mode.name == 'password':
629 self.input_ = self.password
630 elif self.mode.name == 'name_thing':
631 if hasattr(self.game.player.carrying, 'name'):
632 self.input_ = self.game.player.carrying.name
633 elif self.mode.name == 'admin_thing_protect':
634 if hasattr(self.game.player.carrying, 'protection'):
635 self.input_ = self.game.player.carrying.protection
636 elif self.mode.name == 'enter_face':
637 start = self.ascii_draw_stage * 6
638 end = (self.ascii_draw_stage + 1) * 6
639 self.input_ = self.game.player.face[start:end]
640 elif self.mode.name == 'enter_design':
641 width = self.game.player.carrying.design[0].x
642 start = self.ascii_draw_stage * width
643 end = (self.ascii_draw_stage + 1) * width
644 self.input_ = self.game.player.carrying.design[1][start:end]
646 def send_tile_control_command(self):
647 self.send('SET_TILE_CONTROL %s %s' %
648 (self.explorer, quote(self.tile_control_char)))
650 def toggle_map_mode(self):
651 if self.map_mode == 'terrain only':
652 self.map_mode = 'terrain + annotations'
653 elif self.map_mode == 'terrain + annotations':
654 self.map_mode = 'terrain + things'
655 elif self.map_mode == 'terrain + things':
656 self.map_mode = 'protections'
657 elif self.map_mode == 'protections':
658 self.map_mode = 'terrain only'
660 def switch_mode(self, mode_name):
662 def fail(msg, return_mode='play'):
665 self.switch_mode(return_mode)
667 if self.mode and self.mode.name == 'control_tile_draw':
668 self.log('@ finished tile protection drawing.')
669 self.draw_face = False
670 self.tile_draw = False
671 self.ascii_draw_stage = 0
672 self.full_ascii_draw = ''
673 if mode_name == 'command_thing' and\
674 (not self.game.player.carrying or
675 not self.game.player.carrying.commandable):
676 return fail('not carrying anything commandable')
677 if mode_name == 'name_thing' and not self.game.player.carrying:
678 return fail('not carrying anything to re-name', 'edit')
679 if mode_name == 'admin_thing_protect' and not self.game.player.carrying:
680 return fail('not carrying anything to protect')
681 if mode_name == 'take_thing' and self.game.player.carrying:
682 return fail('already carrying something')
683 if mode_name == 'drop_thing' and not self.game.player.carrying:
684 return fail('not carrying anything droppable')
685 if mode_name == 'enter_design' and\
686 (not self.game.player.carrying or
687 not hasattr(self.game.player.carrying, 'design')):
688 return fail('not carrying designable to edit', 'edit')
689 if mode_name == 'admin_enter' and self.is_admin:
691 self.mode = getattr(self, 'mode_' + mode_name)
692 if self.mode.name in {'control_tile_draw', 'control_tile_type',
694 self.map_mode = 'protections'
695 elif self.mode.name != 'edit':
696 self.map_mode = 'terrain + things'
697 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
698 self.explorer = YX(self.game.player.position.y,
699 self.game.player.position.x)
700 if self.mode.is_single_char_entry:
701 self.show_help = True
702 if len(self.mode.intro_msg) > 0:
703 self.log(self.mode.intro_msg)
704 if self.mode.name == 'login':
706 self.send('LOGIN ' + quote(self.login_name))
708 self.log('@ enter username')
709 elif self.mode.name == 'take_thing':
710 self.log('Portable things in reach for pick-up:')
712 'HERE': YX(0, 0), 'LEFT': YX(0, -1), 'RIGHT': YX(0, 1)
714 if type(self.game.map_geometry) == MapGeometrySquare:
715 directed_moves['UP'] = YX(-1, 0)
716 directed_moves['DOWN'] = YX(1, 0)
717 elif type(self.game.map_geometry) == MapGeometryHex:
718 if self.game.player.position.y % 2:
719 directed_moves['UPLEFT'] = YX(-1, 0)
720 directed_moves['UPRIGHT'] = YX(-1, 1)
721 directed_moves['DOWNLEFT'] = YX(1, 0)
722 directed_moves['DOWNRIGHT'] = YX(1, 1)
724 directed_moves['UPLEFT'] = YX(-1, -1)
725 directed_moves['UPRIGHT'] = YX(-1, 0)
726 directed_moves['DOWNLEFT'] = YX(1, -1)
727 directed_moves['DOWNRIGHT'] = YX(1, 0)
729 for direction in directed_moves:
730 move = directed_moves[direction]
731 select_range[direction] = self.game.player.position + move
732 self.selectables = []
734 for direction in select_range:
735 for t in [t for t in self.game.things
736 if t.portable and t.position == select_range[direction]]:
737 self.selectables += [t.id_]
738 directions += [direction]
739 if len(self.selectables) == 0:
740 return fail('nothing to pick-up')
742 for i in range(len(self.selectables)):
743 t = self.game.get_thing(self.selectables[i])
744 self.log('%s %s: %s' % (i, directions[i],
745 self.get_thing_info(t)))
746 elif self.mode.name == 'drop_thing':
747 self.log('Direction to drop thing to:')
749 ['HERE'] + list(self.game.tui.movement_keys.values())
750 for i in range(len(self.selectables)):
751 self.log(str(i) + ': ' + self.selectables[i])
752 elif self.mode.name == 'enter_design':
753 if self.game.player.carrying.type_ == 'Hat':
754 self.log('@ The design you enter must be %s lines of max %s '
755 'characters width each'
756 % (self.game.player.carrying.design[0].y,
757 self.game.player.carrying.design[0].x))
758 self.log('@ Legal characters: ' + self.game.players_hat_chars)
759 self.log('@ (Eat cookies to extend the ASCII characters available for drawing.)')
761 self.log('@ Width of first line determines maximum width for remaining design')
762 self.log('@ Finish design by entering an empty line (multiple space characters do not count as empty)')
763 elif self.mode.name == 'command_thing':
764 self.send('TASK:COMMAND ' + quote('HELP'))
765 elif self.mode.name == 'control_pw_pw':
766 self.log('@ enter protection password for "%s":' % self.tile_control_char)
767 elif self.mode.name == 'control_tile_draw':
768 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']))
770 self.restore_input_values()
772 def set_default_colors(self):
773 if curses.can_change_color():
774 curses.init_color(7, 1000, 1000, 1000)
775 curses.init_color(0, 0, 0, 0)
776 self.do_refresh = True
778 def set_random_colors(self):
782 return int(offset + random.random()*375)
784 if curses.can_change_color():
785 curses.init_color(7, rand(625), rand(625), rand(625))
786 curses.init_color(0, rand(0), rand(0), rand(0))
787 self.do_refresh = True
791 return self.info_cached
792 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
794 if len(self.game.fov) > pos_i and self.game.fov[pos_i] != '.':
795 info_to_cache += 'outside field of view'
797 for t in self.game.things:
798 if t.position == self.explorer:
799 info_to_cache += '%s' % self.get_thing_info(t, True)
800 terrain_char = self.game.map_content[pos_i]
802 if terrain_char in self.game.terrains:
803 terrain_desc = self.game.terrains[terrain_char]
804 info_to_cache += 'TERRAIN: %s (%s' % (terrain_char,
806 protection = self.game.map_control_content[pos_i]
807 if protection != '.':
808 info_to_cache += '/protection:%s' % protection
809 info_to_cache += ')\n'
810 if self.explorer in self.game.portals:
811 info_to_cache += 'PORTAL: ' +\
812 self.game.portals[self.explorer] + '\n'
813 if self.explorer in self.game.annotations:
814 info_to_cache += 'ANNOTATION: ' +\
815 self.game.annotations[self.explorer]
816 self.info_cached = info_to_cache
817 return self.info_cached
819 def get_thing_info(self, t, detailed=False):
823 info += self.game.thing_types[t.type_]
824 if hasattr(t, 'thing_char'):
826 if hasattr(t, 'name'):
827 info += ': %s' % t.name
828 info += ' (%s' % t.type_
829 if hasattr(t, 'installed'):
831 if t.type_ == 'Bottle':
832 if t.thing_char == '_':
834 elif t.thing_char == '~':
837 protection = t.protection
838 if protection != '.':
839 info += '/protection:%s' % protection
841 if hasattr(t, 'hat') or hasattr(t, 'face'):
842 info += '----------\n'
843 if hasattr(t, 'hat'):
844 info += '| %s |\n' % t.hat[0:6]
845 info += '| %s |\n' % t.hat[6:12]
846 info += '| %s |\n' % t.hat[12:18]
847 if hasattr(t, 'face'):
848 info += '| %s |\n' % t.face[0:6]
849 info += '| %s |\n' % t.face[6:12]
850 info += '| %s |\n' % t.face[12:18]
851 info += '----------\n'
852 if hasattr(t, 'design'):
853 line_length = t.design[0].x
855 for i in range(t.design[0].y):
856 start = i * line_length
857 end = (i + 1) * line_length
858 lines += [t.design[1][start:end]]
859 info += '-' * (line_length + 4) + '\n'
861 info += '| %s |\n' % line
862 info += '-' * (line_length + 4) + '\n'
867 def reset_size(self):
869 self.left_window_width = min(52, int(self.size.x / 2))
870 self.right_window_width = self.size.x - self.left_window_width
872 def addstr(self, y, x, line, ignore=None):
873 super().addstr(y, x, line, curses.color_pair(1))
876 self.switch_mode('waiting_for_server')
878 self.set_default_colors()
879 curses.init_pair(1, 7, 0)
880 if not curses.can_change_color():
881 self.log('@ unfortunately, your terminal does not seem to '
882 'support re-definition of colors; you might miss out '
883 'on some color effects')
888 def handle_input(msg):
889 command, args = self.parser.parse(msg)
892 def task_action_on(action):
893 return self.action_tasks[action] in self.game.tasks
895 def recalc_input_lines():
896 if not self.mode.has_input_prompt:
897 self.input_lines = []
899 self.input_lines = msg_into_lines_of_width(self.input_prompt
901 self.right_window_width)
903 def move_explorer(direction):
904 target = self.game.map_geometry.move_yx(self.explorer, direction)
906 self.info_cached = None
907 self.explorer = target
909 self.send_tile_control_command()
915 for line in self._log:
916 lines += msg_into_lines_of_width(line, self.right_window_width)
919 max_y = self.size.y - len(self.input_lines)
920 for i in range(len(lines)):
921 if (i >= max_y - height_header):
923 self.addstr(max_y - i - 1, self.left_window_width, lines[i])
926 info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info())
927 lines = msg_into_lines_of_width(info, self.right_window_width)
929 for i in range(len(lines)):
930 y = height_header + i
931 if y >= self.size.y - len(self.input_lines):
933 self.addstr(y, self.left_window_width, lines[i])
936 y = self.size.y - len(self.input_lines)
937 for i in range(len(self.input_lines)):
938 self.addstr(y, self.left_window_width, self.input_lines[i])
942 stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy,
943 self.game.bladder_pressure)
944 self.addstr(0, self.left_window_width, stats)
947 help = "hit [%s] for help" % self.keys['help']
948 if self.mode.has_input_prompt:
949 help = "enter /help for help"
950 self.addstr(1, self.left_window_width,
951 'MODE: %s – %s' % (self.mode.short_desc, help))
954 if (not self.game.turn_complete) and len(self.map_lines) == 0:
956 if self.game.turn_complete:
958 for y in range(self.game.map_geometry.size.y):
959 start = self.game.map_geometry.size.x * y
960 end = start + self.game.map_geometry.size.x
961 if self.map_mode == 'protections':
962 map_lines_split += [[c + ' ' for c
963 in self.game.map_control_content[start:end]]]
965 map_lines_split += [[c + ' ' for c
966 in self.game.map_content[start:end]]]
967 if self.map_mode == 'terrain + annotations':
968 for p in self.game.annotations:
969 map_lines_split[p.y][p.x] = 'A '
970 elif self.map_mode == 'terrain + things':
971 for p in self.game.portals.keys():
972 original = map_lines_split[p.y][p.x]
973 map_lines_split[p.y][p.x] = original[0] + 'P'
976 def draw_thing(t, used_positions):
977 symbol = self.game.thing_types[t.type_]
979 if hasattr(t, 'thing_char'):
980 meta_char = t.thing_char
981 if t.position in used_positions:
983 if hasattr(t, 'carrying') and t.carrying:
985 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
986 used_positions += [t.position]
988 for t in [t for t in self.game.things if t.type_ != 'Player']:
989 draw_thing(t, used_positions)
990 for t in [t for t in self.game.things if t.type_ == 'Player']:
991 draw_thing(t, used_positions)
992 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
993 map_lines_split[self.explorer.y][self.explorer.x] = '??'
994 elif self.map_mode != 'terrain + things':
995 map_lines_split[self.game.player.position.y]\
996 [self.game.player.position.x] = '??'
998 if type(self.game.map_geometry) == MapGeometryHex:
1000 for line in map_lines_split:
1001 self.map_lines += [indent * ' ' + ''.join(line)]
1002 indent = 0 if indent else 1
1004 for line in map_lines_split:
1005 self.map_lines += [''.join(line)]
1006 window_center = YX(int(self.size.y / 2),
1007 int(self.left_window_width / 2))
1008 center = self.game.player.position
1009 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
1010 center = self.explorer
1011 center = YX(center.y, center.x * 2)
1012 self.offset = center - window_center
1013 if type(self.game.map_geometry) == MapGeometryHex and self.offset.y % 2:
1014 self.offset += YX(0, 1)
1015 term_y = max(0, -self.offset.y)
1016 term_x = max(0, -self.offset.x)
1017 map_y = max(0, self.offset.y)
1018 map_x = max(0, self.offset.x)
1019 while term_y < self.size.y and map_y < len(self.map_lines):
1020 to_draw = self.map_lines[map_y][map_x:self.left_window_width + self.offset.x]
1021 self.addstr(term_y, term_x, to_draw)
1026 players = [t for t in self.game.things if t.type_ == 'Player']
1027 players.sort(key=lambda t: len(t.name))
1029 shrink_offset = max(0, (self.size.y - self.left_window_width // 2) // 2)
1032 offset_y = y - shrink_offset
1033 max_len = max(5, (self.left_window_width // 2) - (offset_y * 2) - 8)
1035 if len(name) > max_len:
1036 name = name[:max_len - 1] + '…'
1037 self.addstr(y, 0, '@%s:%s' % (t.thing_char, name))
1039 if y >= self.size.y:
1042 def draw_face_popup():
1043 t = self.game.get_thing(self.draw_face)
1044 if not t or not hasattr(t, 'face'):
1045 self.draw_face = False
1048 start_x = self.left_window_width - 10
1049 def draw_body_part(body_part, end_y):
1050 self.addstr(end_y - 3, start_x, '----------')
1051 self.addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |')
1052 self.addstr(end_y - 1, start_x, '| ' + body_part[6:12] + ' |')
1053 self.addstr(end_y, start_x, '| ' + body_part[12:18] + ' |')
1055 if hasattr(t, 'face'):
1056 draw_body_part(t.face, self.size.y - 3)
1057 if hasattr(t, 'hat'):
1058 draw_body_part(t.hat, self.size.y - 6)
1059 self.addstr(self.size.y - 2, start_x, '----------')
1062 name = name[:6 - 1] + '…'
1063 self.addstr(self.size.y - 1, start_x, '@%s:%s' % (t.thing_char, name))
1066 content = "%s help\n\n%s\n\n" % (self.mode.short_desc,
1067 self.mode.help_intro)
1068 if len(self.mode.available_actions) > 0:
1069 content += "Available actions:\n"
1070 for action in self.mode.available_actions:
1071 if action in self.action_tasks:
1072 if self.action_tasks[action] not in self.game.tasks:
1074 if action == 'move_explorer':
1076 if action == 'move':
1077 key = ','.join(self.movement_keys)
1079 key = self.keys[action]
1080 content += '[%s] – %s\n' % (key, self.action_descriptions[action])
1082 content += self.mode.list_available_modes(self)
1083 for i in range(self.size.y):
1085 self.left_window_width * (not self.mode.has_input_prompt),
1086 ' ' * self.left_window_width)
1088 for line in content.split('\n'):
1089 lines += msg_into_lines_of_width(line, self.right_window_width)
1090 for i in range(len(lines)):
1091 if i >= self.size.y:
1094 self.left_window_width * (not self.mode.has_input_prompt),
1099 self.stdscr.bkgd(' ', curses.color_pair(1))
1100 recalc_input_lines()
1101 if self.mode.has_input_prompt:
1103 if self.mode.shows_info:
1108 if not self.mode.is_intro:
1113 if self.mode.name in {'chat', 'play'}:
1118 def pick_selectable(task_name):
1120 i = int(self.input_)
1121 if i < 0 or i >= len(self.selectables):
1122 self.log('? invalid index, aborted')
1124 self.send('TASK:%s %s' % (task_name, self.selectables[i]))
1126 self.log('? invalid index, aborted')
1128 self.switch_mode('play')
1130 def enter_ascii_art(command, height, width,
1131 with_pw=False, with_size=False):
1132 if with_size and self.ascii_draw_stage == 0:
1133 width = len(self.input_)
1135 self.log('? input too long, must be max 36; try again')
1136 # TODO: move max width mechanism server-side
1138 old_size = self.game.player.carrying.design[0]
1139 if width != old_size.x:
1140 # TODO: save remaining design?
1141 self.game.player.carrying.design[1] = ''
1142 self.game.player.carrying.design[0] = YX(old_size.y, width)
1143 elif len(self.input_) > width:
1144 self.log('? input too long, '
1145 'must be max %s; try again' % width)
1147 self.log(' ' + self.input_)
1148 if with_size and self.input_ in {'', ' '}\
1149 and self.ascii_draw_stage > 0:
1150 height = self.ascii_draw_stage
1153 height = self.ascii_draw_stage + 2
1154 if len(self.input_) < width:
1155 self.input_ += ' ' * (width - len(self.input_))
1156 self.full_ascii_draw += self.input_
1158 old_size = self.game.player.carrying.design[0]
1159 self.game.player.carrying.design[0] = YX(height, old_size.x)
1160 self.ascii_draw_stage += 1
1161 if self.ascii_draw_stage < height:
1162 self.restore_input_values()
1164 if with_pw and with_size:
1165 self.send('%s_SIZE %s %s' % (command, YX(height, width),
1166 quote(self.password)))
1168 self.send('%s %s %s' % (command, quote(self.full_ascii_draw),
1169 quote(self.password)))
1171 self.send('%s %s' % (command, quote(self.full_ascii_draw)))
1172 self.full_ascii_draw = ""
1173 self.ascii_draw_stage = 0
1175 self.switch_mode('edit')
1177 prev_disconnected = self.socket.disconnected
1178 self.socket.keep_connection_alive()
1179 if prev_disconnected and not self.socket.disconnected:
1180 self.update_on_connect()
1186 self.do_refresh = False
1187 for msg in self.socket.get_message():
1190 key = self.stdscr.getkey()
1191 self.do_refresh = True
1192 except curses.error:
1197 # workaround for <https://stackoverflow.com/a/56390915>
1198 if self.store_widechar:
1199 self.store_widechar = False
1200 key = bytes([195, keycode]).decode()
1202 self.store_widechar = True
1204 self.show_help = False
1205 self.draw_face = False
1206 if key == 'KEY_RESIZE':
1208 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
1209 self.input_ = self.input_[:-1]
1210 elif (((not self.mode.is_intro) and keycode == 27) # Escape
1211 or (self.mode.has_input_prompt and key == '\n'
1212 and self.input_ == ''\
1213 and self.mode.name in {'chat', 'command_thing',
1214 'take_thing', 'drop_thing',
1216 if self.mode.name not in {'chat', 'play', 'study', 'edit'}:
1217 self.log('@ aborted')
1218 self.switch_mode('play')
1219 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
1220 self.show_help = True
1222 self.restore_input_values()
1223 elif self.mode.has_input_prompt and key != '\n': # Return key
1225 max_length = self.right_window_width * self.size.y - len(self.input_prompt) - 1
1226 if len(self.input_) > max_length:
1227 self.input_ = self.input_[:max_length]
1228 elif key == self.keys['help'] and not self.mode.is_single_char_entry:
1229 self.show_help = True
1230 elif self.mode.name == 'login' and key == '\n':
1231 self.login_name = self.input_
1232 self.send('LOGIN ' + quote(self.input_))
1234 elif self.mode.name == 'enter_face' and key == '\n':
1235 enter_ascii_art('PLAYER_FACE', 3, 6)
1236 elif self.mode.name == 'enter_design' and key == '\n':
1237 if self.game.player.carrying.type_ == 'Hat':
1238 enter_ascii_art('THING_DESIGN',
1239 self.game.player.carrying.design[0].y,
1240 self.game.player.carrying.design[0].x, True)
1242 enter_ascii_art('THING_DESIGN',
1243 self.game.player.carrying.design[0].y,
1244 self.game.player.carrying.design[0].x,
1246 elif self.mode.name == 'take_thing' and key == '\n':
1247 pick_selectable('PICK_UP')
1248 elif self.mode.name == 'drop_thing' and key == '\n':
1249 pick_selectable('DROP')
1250 elif self.mode.name == 'command_thing' and key == '\n':
1251 self.send('TASK:COMMAND ' + quote(self.input_))
1253 elif self.mode.name == 'control_pw_pw' and key == '\n':
1254 if self.input_ == '':
1255 self.log('@ aborted')
1257 self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
1258 self.log('@ sent new password for protection character "%s"' % self.tile_control_char)
1259 self.switch_mode('admin')
1260 elif self.mode.name == 'password' and key == '\n':
1261 if self.input_ == '':
1263 self.password = self.input_
1264 self.switch_mode('edit')
1265 elif self.mode.name == 'admin_enter' and key == '\n':
1266 self.send('BECOME_ADMIN ' + quote(self.input_))
1267 self.switch_mode('play')
1268 elif self.mode.name == 'control_pw_type' and key == '\n':
1269 if len(self.input_) != 1:
1270 self.log('@ entered non-single-char, therefore aborted')
1271 self.switch_mode('admin')
1273 self.tile_control_char = self.input_
1274 self.switch_mode('control_pw_pw')
1275 elif self.mode.name == 'admin_thing_protect' and key == '\n':
1276 if len(self.input_) != 1:
1277 self.log('@ entered non-single-char, therefore aborted')
1279 self.send('THING_PROTECTION %s' % (quote(self.input_)))
1280 self.log('@ sent new protection character for thing')
1281 self.switch_mode('admin')
1282 elif self.mode.name == 'control_tile_type' and key == '\n':
1283 if len(self.input_) != 1:
1284 self.log('@ entered non-single-char, therefore aborted')
1285 self.switch_mode('admin')
1287 self.tile_control_char = self.input_
1288 self.switch_mode('control_tile_draw')
1289 elif self.mode.name == 'chat' and key == '\n':
1290 if self.input_ == '':
1292 if self.input_[0] == '/':
1293 if self.input_.startswith('/nick'):
1294 tokens = self.input_.split(maxsplit=1)
1295 if len(tokens) == 2:
1296 self.send('NICK ' + quote(tokens[1]))
1298 self.log('? need login name')
1300 self.log('? unknown command')
1302 self.send('ALL ' + quote(self.input_))
1304 elif self.mode.name == 'name_thing' and key == '\n':
1305 if self.input_ == '':
1307 self.send('THING_NAME %s %s' % (quote(self.input_),
1308 quote(self.password)))
1309 self.switch_mode('edit')
1310 elif self.mode.name == 'annotate' and key == '\n':
1311 if self.input_ == '':
1313 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
1314 quote(self.password)))
1315 self.switch_mode('edit')
1316 elif self.mode.name == 'portal' and key == '\n':
1317 if self.input_ == '':
1319 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
1320 quote(self.password)))
1321 self.switch_mode('edit')
1322 elif self.mode.name == 'study':
1323 if self.mode.mode_switch_on_key(self, key):
1325 elif key == self.keys['toggle_map_mode']:
1326 self.toggle_map_mode()
1327 elif key in self.movement_keys:
1328 move_explorer(self.movement_keys[key])
1329 elif self.mode.name == 'play':
1330 if self.mode.mode_switch_on_key(self, key):
1332 elif key == self.keys['door'] and task_action_on('door'):
1333 self.send('TASK:DOOR')
1334 elif key == self.keys['consume'] and task_action_on('consume'):
1335 self.send('TASK:INTOXICATE')
1336 elif key == self.keys['wear'] and task_action_on('wear'):
1337 self.send('TASK:WEAR')
1338 elif key == self.keys['spin'] and task_action_on('spin'):
1339 self.send('TASK:SPIN')
1340 elif key == self.keys['dance'] and task_action_on('dance'):
1341 self.send('TASK:DANCE')
1342 elif key == self.keys['teleport']:
1343 if self.game.player.position in self.game.portals:
1344 self.socket.host = self.game.portals[self.game.player.position]
1348 self.log('? not standing on portal')
1349 elif key in self.movement_keys and task_action_on('move'):
1350 self.send('TASK:MOVE ' + self.movement_keys[key])
1351 elif self.mode.name == 'write':
1352 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
1353 self.switch_mode('edit')
1354 elif self.mode.name == 'control_tile_draw':
1355 if self.mode.mode_switch_on_key(self, key):
1357 elif key in self.movement_keys:
1358 move_explorer(self.movement_keys[key])
1359 elif key == self.keys['toggle_tile_draw']:
1360 self.tile_draw = False if self.tile_draw else True
1361 elif self.mode.name == 'admin':
1362 if self.mode.mode_switch_on_key(self, key):
1364 elif key == self.keys['toggle_map_mode']:
1365 self.toggle_map_mode()
1366 elif key in self.movement_keys and task_action_on('move'):
1367 self.send('TASK:MOVE ' + self.movement_keys[key])
1368 elif self.mode.name == 'edit':
1369 if self.mode.mode_switch_on_key(self, key):
1371 elif key == self.keys['flatten'] and task_action_on('flatten'):
1372 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
1373 elif key == self.keys['install'] and task_action_on('install'):
1374 self.send('TASK:INSTALL %s' % quote(self.password))
1375 elif key == self.keys['toggle_map_mode']:
1376 self.toggle_map_mode()
1377 elif key in self.movement_keys and task_action_on('move'):
1378 self.send('TASK:MOVE ' + self.movement_keys[key])
1380 if len(sys.argv) != 2:
1381 raise ArgError('wrong number of arguments, need game host')