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
306 cmd_THING_TYPE.argtypes = 'string char'
308 def cmd_THING_INSTALLED(game, thing_id):
309 game.get_thing_temp(thing_id).installed = True
310 cmd_THING_INSTALLED.argtypes = 'int:pos'
312 def cmd_THING_CARRYING(game, thing_id, carried_id):
313 game.get_thing_temp(thing_id).carrying = game.get_thing_temp(carried_id)
314 cmd_THING_CARRYING.argtypes = 'int:pos int:pos'
316 def cmd_TERRAIN(game, terrain_char, terrain_desc):
317 game.terrains[terrain_char] = terrain_desc
318 cmd_TERRAIN.argtypes = 'char string'
322 cmd_PONG.argtypes = ''
324 def cmd_DEFAULT_COLORS(game):
325 game.tui.set_default_colors()
326 cmd_DEFAULT_COLORS.argtypes = ''
328 def cmd_RANDOM_COLORS(game):
329 game.tui.set_random_colors()
330 cmd_RANDOM_COLORS.argtypes = ''
332 def cmd_STATS(game, bladder_pressure, energy):
333 game.bladder_pressure_new = bladder_pressure
334 game.energy_new = energy
335 cmd_STATS.argtypes = 'int:nonneg int'
337 class Game(GameBase):
338 turn_complete = False
343 def __init__(self, *args, **kwargs):
344 super().__init__(*args, **kwargs)
345 self.register_command(cmd_LOGIN_OK)
346 self.register_command(cmd_ADMIN_OK)
347 self.register_command(cmd_PONG)
348 self.register_command(cmd_CHAT)
349 self.register_command(cmd_CHATFACE)
350 self.register_command(cmd_REPLY)
351 self.register_command(cmd_PLAYER_ID)
352 self.register_command(cmd_TURN)
353 self.register_command(cmd_OTHER_WIPE)
354 self.register_command(cmd_THING)
355 self.register_command(cmd_THING_TYPE)
356 self.register_command(cmd_THING_NAME)
357 self.register_command(cmd_THING_CHAR)
358 self.register_command(cmd_THING_FACE)
359 self.register_command(cmd_THING_HAT)
360 self.register_command(cmd_THING_DESIGN)
361 self.register_command(cmd_THING_CARRYING)
362 self.register_command(cmd_THING_INSTALLED)
363 self.register_command(cmd_TERRAIN)
364 self.register_command(cmd_MAP)
365 self.register_command(cmd_MAP_CONTROL)
366 self.register_command(cmd_PORTAL)
367 self.register_command(cmd_ANNOTATION)
368 self.register_command(cmd_GAME_STATE_COMPLETE)
369 self.register_command(cmd_PLAYERS_HAT_CHARS)
370 self.register_command(cmd_ARGUMENT_ERROR)
371 self.register_command(cmd_GAME_ERROR)
372 self.register_command(cmd_PLAY_ERROR)
373 self.register_command(cmd_TASKS)
374 self.register_command(cmd_FOV)
375 self.register_command(cmd_DEFAULT_COLORS)
376 self.register_command(cmd_RANDOM_COLORS)
377 self.register_command(cmd_STATS)
378 self.map_content = ''
379 self.players_hat_chars = ''
381 self.annotations = {}
382 self.annotations_new = {}
384 self.portals_new = {}
387 self.parser = Parser(self)
390 def train_parser(self):
391 self.parser.string_options = {
392 'map_geometry': {'Hex', 'Square'},
393 'thing_type': self.thing_types.keys()
396 def get_command(self, command_name):
397 from functools import partial
398 f = partial(self.commands[command_name], self)
399 f.argtypes = self.commands[command_name].argtypes
402 def get_thing_temp(self, id_):
403 for thing in self.things_new:
410 def __init__(self, name, has_input_prompt=False, shows_info=False,
411 is_intro=False, is_single_char_entry=False):
413 self.short_desc = mode_helps[name]['short']
414 self.available_modes = []
415 self.available_actions = []
416 self.has_input_prompt = has_input_prompt
417 self.shows_info = shows_info
418 self.is_intro = is_intro
419 self.help_intro = mode_helps[name]['long']
420 self.intro_msg = mode_helps[name]['intro']
421 self.is_single_char_entry = is_single_char_entry
424 def iter_available_modes(self, tui):
425 for mode_name in self.available_modes:
426 mode = getattr(tui, 'mode_' + mode_name)
429 key = tui.keys['switch_to_' + mode.name]
432 def list_available_modes(self, tui):
434 if len(self.available_modes) > 0:
435 msg = 'Other modes available from here:\n'
436 for mode, key in self.iter_available_modes(tui):
437 msg += '[%s] – %s\n' % (key, mode.short_desc)
440 def mode_switch_on_key(self, tui, key_pressed):
441 for mode, key in self.iter_available_modes(tui):
442 if key_pressed == key:
443 tui.switch_mode(mode.name)
447 class RogueChatTUI(TUI):
448 mode_admin_enter = Mode('admin_enter', has_input_prompt=True)
449 mode_admin = Mode('admin')
450 mode_play = Mode('play')
451 mode_study = Mode('study', shows_info=True)
452 mode_write = Mode('write', is_single_char_entry=True)
453 mode_edit = Mode('edit')
454 mode_control_pw_type = Mode('control_pw_type', has_input_prompt=True)
455 mode_control_pw_pw = Mode('control_pw_pw', has_input_prompt=True)
456 mode_control_tile_type = Mode('control_tile_type', has_input_prompt=True)
457 mode_control_tile_draw = Mode('control_tile_draw')
458 mode_admin_thing_protect = Mode('admin_thing_protect', has_input_prompt=True)
459 mode_annotate = Mode('annotate', has_input_prompt=True, shows_info=True)
460 mode_portal = Mode('portal', has_input_prompt=True, shows_info=True)
461 mode_chat = Mode('chat', has_input_prompt=True)
462 mode_waiting_for_server = Mode('waiting_for_server', is_intro=True)
463 mode_login = Mode('login', has_input_prompt=True, is_intro=True)
464 mode_post_login_wait = Mode('post_login_wait', is_intro=True)
465 mode_password = Mode('password', has_input_prompt=True)
466 mode_name_thing = Mode('name_thing', has_input_prompt=True, shows_info=True)
467 mode_command_thing = Mode('command_thing', has_input_prompt=True)
468 mode_take_thing = Mode('take_thing', has_input_prompt=True)
469 mode_drop_thing = Mode('drop_thing', has_input_prompt=True)
470 mode_enter_face = Mode('enter_face', has_input_prompt=True)
471 mode_enter_design = Mode('enter_design', has_input_prompt=True)
475 def __init__(self, host, *args, **kwargs):
478 self.mode_play.available_modes = ["chat", "study", "edit", "admin_enter",
479 "command_thing", "take_thing",
481 self.mode_play.available_actions = ["move", "teleport", "door", "consume",
482 "install", "wear", "spin", "dance"]
483 self.mode_study.available_modes = ["chat", "play", "admin_enter", "edit"]
484 self.mode_study.available_actions = ["toggle_map_mode", "move_explorer"]
485 self.mode_admin.available_modes = ["admin_thing_protect", "control_pw_type",
486 "control_tile_type", "chat",
487 "study", "play", "edit"]
488 self.mode_admin.available_actions = ["move", "toggle_map_mode"]
489 self.mode_control_tile_draw.available_modes = ["admin_enter"]
490 self.mode_control_tile_draw.available_actions = ["move_explorer",
492 self.mode_edit.available_modes = ["write", "annotate", "portal",
493 "name_thing", "enter_face", "enter_design",
495 "chat", "study", "play", "admin_enter"]
496 self.mode_edit.available_actions = ["move", "flatten", "install",
499 self.socket = ClientSocket(host, self.socket_log)
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.input_prompt = '> '
562 self.action_descriptions = {
564 'flatten': 'flatten surroundings',
565 'teleport': 'teleport',
566 'take_thing': 'pick up thing',
567 'drop_thing': 'drop thing',
568 'toggle_map_mode': 'toggle map view',
569 'toggle_tile_draw': 'toggle protection character drawing',
570 'install': '(un-)install',
572 'door': 'open/close',
573 'consume': 'consume',
577 self.action_tasks = {
578 'flatten': 'FLATTEN_SURROUNDINGS',
579 'take_thing': 'PICK_UP',
580 'drop_thing': 'DROP',
582 'install': 'INSTALL',
585 'command': 'COMMAND',
586 'consume': 'INTOXICATE',
590 super().__init__(*args, **kwargs)
592 def update_on_connect(self):
593 self.game.thing_types = {}
594 self.game.terrains = {}
595 self.game.train_parser()
596 self.is_admin = False
597 self.socket.send('TASKS')
598 self.socket.send('TERRAINS')
599 self.socket.send('THING_TYPES')
600 self.switch_mode('login')
604 self.log('@ attempting reconnect')
605 self.socket.send('QUIT')
606 # necessitated by some strange SSL race conditions with ws4py
607 time.sleep(0.1) # FIXME find out why exactly necessary
608 self.switch_mode('waiting_for_server')
609 self.socket.connect()
610 self.update_on_connect()
613 self.socket.send(msg)
614 if self.socket.disconnected:
615 self.do_refresh = True
617 def socket_log(self, msg):
620 def log_msg(self, msg):
623 if len(self._log) > 100:
624 self.log = self._log[-100:]
626 def restore_input_values(self):
627 if self.mode.name == 'annotate' and self.explorer in self.game.annotations:
628 self.input_ = self.game.annotations[self.explorer]
629 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
630 self.input_ = self.game.portals[self.explorer]
631 elif self.mode.name == 'password':
632 self.input_ = self.password
633 elif self.mode.name == 'name_thing':
634 if hasattr(self.game.player.carrying, 'name'):
635 self.input_ = self.game.player.carrying.name
636 elif self.mode.name == 'admin_thing_protect':
637 if hasattr(self.game.player.carrying, 'protection'):
638 self.input_ = self.game.player.carrying.protection
639 elif self.mode.name == 'enter_face':
640 start = self.ascii_draw_stage * 6
641 end = (self.ascii_draw_stage + 1) * 6
642 self.input_ = self.game.player.face[start:end]
643 elif self.mode.name == 'enter_design':
644 width = self.game.player.carrying.design[0].x
645 start = self.ascii_draw_stage * width
646 end = (self.ascii_draw_stage + 1) * width
647 self.input_ = self.game.player.carrying.design[1][start:end]
649 def send_tile_control_command(self):
650 self.send('SET_TILE_CONTROL %s %s' %
651 (self.explorer, quote(self.tile_control_char)))
653 def toggle_map_mode(self):
654 if self.map_mode == 'terrain only':
655 self.map_mode = 'terrain + annotations'
656 elif self.map_mode == 'terrain + annotations':
657 self.map_mode = 'terrain + things'
658 elif self.map_mode == 'terrain + things':
659 self.map_mode = 'protections'
660 elif self.map_mode == 'protections':
661 self.map_mode = 'terrain only'
663 def switch_mode(self, mode_name):
665 def fail(msg, return_mode='play'):
668 self.switch_mode(return_mode)
670 if self.mode and self.mode.name == 'control_tile_draw':
671 self.log('@ finished tile protection drawing.')
672 self.draw_face = False
673 self.tile_draw = False
674 self.ascii_draw_stage = 0
675 self.full_ascii_draw = ''
676 if mode_name == 'command_thing' and\
677 (not self.game.player.carrying or
678 not self.game.player.carrying.commandable):
679 return fail('not carrying anything commandable')
680 if mode_name == 'name_thing' and not self.game.player.carrying:
681 return fail('not carrying anything to re-name', 'edit')
682 if mode_name == 'admin_thing_protect' and not self.game.player.carrying:
683 return fail('not carrying anything to protect')
684 if mode_name == 'take_thing' and self.game.player.carrying:
685 return fail('already carrying something')
686 if mode_name == 'drop_thing' and not self.game.player.carrying:
687 return fail('not carrying anything droppable')
688 if mode_name == 'enter_design' and\
689 (not self.game.player.carrying or
690 not hasattr(self.game.player.carrying, 'design')):
691 return fail('not carrying designable to edit', 'edit')
692 if mode_name == 'admin_enter' and self.is_admin:
694 self.mode = getattr(self, 'mode_' + mode_name)
695 if self.mode.name in {'control_tile_draw', 'control_tile_type',
697 self.map_mode = 'protections'
698 elif self.mode.name != 'edit':
699 self.map_mode = 'terrain + things'
700 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
701 self.explorer = YX(self.game.player.position.y,
702 self.game.player.position.x)
703 if self.mode.is_single_char_entry:
704 self.show_help = True
705 if len(self.mode.intro_msg) > 0:
706 self.log(self.mode.intro_msg)
707 if self.mode.name == 'login':
709 self.send('LOGIN ' + quote(self.login_name))
711 self.log('@ enter username')
712 elif self.mode.name == 'take_thing':
713 self.log('Portable things in reach for pick-up:')
715 'HERE': YX(0, 0), 'LEFT': YX(0, -1), 'RIGHT': YX(0, 1)
717 if type(self.game.map_geometry) == MapGeometrySquare:
718 directed_moves['UP'] = YX(-1, 0)
719 directed_moves['DOWN'] = YX(1, 0)
720 elif type(self.game.map_geometry) == MapGeometryHex:
721 if self.game.player.position.y % 2:
722 directed_moves['UPLEFT'] = YX(-1, 0)
723 directed_moves['UPRIGHT'] = YX(-1, 1)
724 directed_moves['DOWNLEFT'] = YX(1, 0)
725 directed_moves['DOWNRIGHT'] = YX(1, 1)
727 directed_moves['UPLEFT'] = YX(-1, -1)
728 directed_moves['UPRIGHT'] = YX(-1, 0)
729 directed_moves['DOWNLEFT'] = YX(1, -1)
730 directed_moves['DOWNRIGHT'] = YX(1, 0)
732 for direction in directed_moves:
733 move = directed_moves[direction]
734 select_range[direction] = self.game.player.position + move
735 self.selectables = []
737 for direction in select_range:
738 for t in [t for t in self.game.things
739 if t.portable and t.position == select_range[direction]]:
740 self.selectables += [t.id_]
741 directions += [direction]
742 if len(self.selectables) == 0:
743 return fail('nothing to pick-up')
745 for i in range(len(self.selectables)):
746 t = self.game.get_thing(self.selectables[i])
747 self.log('%s %s: %s' % (i, directions[i],
748 self.get_thing_info(t)))
749 elif self.mode.name == 'drop_thing':
750 self.log('Direction to drop thing to:')
752 ['HERE'] + list(self.game.tui.movement_keys.values())
753 for i in range(len(self.selectables)):
754 self.log(str(i) + ': ' + self.selectables[i])
755 elif self.mode.name == 'enter_design':
756 if self.game.player.carrying.type_ == 'Hat':
757 self.log('@ The design you enter must be %s lines of max %s '
758 'characters width each'
759 % (self.game.player.carrying.design[0].y,
760 self.game.player.carrying.design[0].x))
761 self.log('@ Legal characters: ' + self.game.players_hat_chars)
762 self.log('@ (Eat cookies to extend the ASCII characters available for drawing.)')
764 self.log('@ Width of first line determines maximum width for remaining design')
765 self.log('@ Finish design by entering an empty line (multiple space characters do not count as empty)')
766 elif self.mode.name == 'command_thing':
767 self.send('TASK:COMMAND ' + quote('HELP'))
768 elif self.mode.name == 'control_pw_pw':
769 self.log('@ enter protection password for "%s":' % self.tile_control_char)
770 elif self.mode.name == 'control_tile_draw':
771 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']))
773 self.restore_input_values()
775 def set_default_colors(self):
776 if curses.can_change_color():
777 curses.init_color(7, 1000, 1000, 1000)
778 curses.init_color(0, 0, 0, 0)
779 self.do_refresh = True
781 def set_random_colors(self):
785 return int(offset + random.random()*375)
787 if curses.can_change_color():
788 curses.init_color(7, rand(625), rand(625), rand(625))
789 curses.init_color(0, rand(0), rand(0), rand(0))
790 self.do_refresh = True
794 return self.info_cached
795 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
797 if len(self.game.fov) > pos_i and self.game.fov[pos_i] != '.':
798 info_to_cache += 'outside field of view'
800 for t in self.game.things:
801 if t.position == self.explorer:
802 info_to_cache += '%s' % self.get_thing_info(t, True)
803 terrain_char = self.game.map_content[pos_i]
805 if terrain_char in self.game.terrains:
806 terrain_desc = self.game.terrains[terrain_char]
807 info_to_cache += 'TERRAIN: %s (%s' % (terrain_char,
809 protection = self.game.map_control_content[pos_i]
810 if protection != '.':
811 info_to_cache += '/protection:%s' % protection
812 info_to_cache += ')\n'
813 if self.explorer in self.game.portals:
814 info_to_cache += 'PORTAL: ' +\
815 self.game.portals[self.explorer] + '\n'
816 if self.explorer in self.game.annotations:
817 info_to_cache += 'ANNOTATION: ' +\
818 self.game.annotations[self.explorer]
819 self.info_cached = info_to_cache
820 return self.info_cached
822 def get_thing_info(self, t, detailed=False):
826 info += self.game.thing_types[t.type_]
827 if hasattr(t, 'thing_char'):
829 if hasattr(t, 'name'):
830 info += ': %s' % t.name
831 info += ' (%s' % t.type_
832 if hasattr(t, 'installed'):
834 if t.type_ == 'Bottle':
835 if t.thing_char == '_':
837 elif t.thing_char == '~':
840 protection = t.protection
841 if protection != '.':
842 info += '/protection:%s' % protection
844 if hasattr(t, 'hat') or hasattr(t, 'face'):
845 info += '----------\n'
846 if hasattr(t, 'hat'):
847 info += '| %s |\n' % t.hat[0:6]
848 info += '| %s |\n' % t.hat[6:12]
849 info += '| %s |\n' % t.hat[12:18]
850 if hasattr(t, 'face'):
851 info += '| %s |\n' % t.face[0:6]
852 info += '| %s |\n' % t.face[6:12]
853 info += '| %s |\n' % t.face[12:18]
854 info += '----------\n'
855 if hasattr(t, 'design'):
856 line_length = t.design[0].x
858 for i in range(t.design[0].y):
859 start = i * line_length
860 end = (i + 1) * line_length
861 lines += [t.design[1][start:end]]
862 info += '-' * (line_length + 4) + '\n'
864 info += '| %s |\n' % line
865 info += '-' * (line_length + 4) + '\n'
870 def reset_size(self):
872 self.left_window_width = min(52, int(self.size.x / 2))
873 self.right_window_width = self.size.x - self.left_window_width
875 def addstr(self, y, x, line, ignore=None):
876 super().addstr(y, x, line, curses.color_pair(1))
879 self.switch_mode('waiting_for_server')
881 self.set_default_colors()
882 curses.init_pair(1, 7, 0)
883 if not curses.can_change_color():
884 self.log('@ unfortunately, your terminal does not seem to '
885 'support re-definition of colors; you might miss out '
886 'on some color effects')
889 def recalc_input_lines(self):
890 if not self.mode.has_input_prompt:
891 self.input_lines = []
893 self.input_lines = msg_into_lines_of_width(self.input_prompt
895 self.right_window_width)
896 def draw_history(self):
898 for line in self._log:
899 lines += msg_into_lines_of_width(line, self.right_window_width)
902 max_y = self.size.y - len(self.input_lines)
903 for i in range(len(lines)):
904 if (i >= max_y - height_header):
906 self.addstr(max_y - i - 1, self.left_window_width, lines[i])
909 info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info())
910 lines = msg_into_lines_of_width(info, self.right_window_width)
912 for i in range(len(lines)):
913 y = height_header + i
914 if y >= self.size.y - len(self.input_lines):
916 self.addstr(y, self.left_window_width, lines[i])
918 def draw_input(self):
919 y = self.size.y - len(self.input_lines)
920 for i in range(len(self.input_lines)):
921 self.addstr(y, self.left_window_width, self.input_lines[i])
924 def draw_stats(self):
925 stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy,
926 self.game.bladder_pressure)
927 self.addstr(0, self.left_window_width, stats)
930 help = "hit [%s] for help" % self.keys['help']
931 if self.mode.has_input_prompt:
932 help = "enter /help for help"
933 self.addstr(1, self.left_window_width,
934 'MODE: %s – %s' % (self.mode.short_desc, help))
937 if (not self.game.turn_complete) and len(self.map_lines) == 0:
939 if self.game.turn_complete:
941 for y in range(self.game.map_geometry.size.y):
942 start = self.game.map_geometry.size.x * y
943 end = start + self.game.map_geometry.size.x
944 if self.map_mode == 'protections':
945 map_lines_split += [[c + ' ' for c
946 in self.game.map_control_content[start:end]]]
948 map_lines_split += [[c + ' ' for c
949 in self.game.map_content[start:end]]]
950 if self.map_mode == 'terrain + annotations':
951 for p in self.game.annotations:
952 map_lines_split[p.y][p.x] = 'A '
953 elif self.map_mode == 'terrain + things':
954 for p in self.game.portals.keys():
955 original = map_lines_split[p.y][p.x]
956 map_lines_split[p.y][p.x] = original[0] + 'P'
959 def draw_thing(t, used_positions):
960 symbol = self.game.thing_types[t.type_]
962 if hasattr(t, 'thing_char'):
963 meta_char = t.thing_char
964 if t.position in used_positions:
966 if hasattr(t, 'carrying') and t.carrying:
968 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
969 used_positions += [t.position]
971 for t in [t for t in self.game.things if t.type_ != 'Player']:
972 draw_thing(t, used_positions)
973 for t in [t for t in self.game.things if t.type_ == 'Player']:
974 draw_thing(t, used_positions)
975 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
976 map_lines_split[self.explorer.y][self.explorer.x] = '??'
977 elif self.map_mode != 'terrain + things':
978 map_lines_split[self.game.player.position.y]\
979 [self.game.player.position.x] = '??'
981 if type(self.game.map_geometry) == MapGeometryHex:
983 for line in map_lines_split:
984 self.map_lines += [indent * ' ' + ''.join(line)]
985 indent = 0 if indent else 1
987 for line in map_lines_split:
988 self.map_lines += [''.join(line)]
989 window_center = YX(int(self.size.y / 2),
990 int(self.left_window_width / 2))
991 center = self.game.player.position
992 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
993 center = self.explorer
994 center = YX(center.y, center.x * 2)
995 self.offset = center - window_center
996 if type(self.game.map_geometry) == MapGeometryHex and self.offset.y % 2:
997 self.offset += YX(0, 1)
998 term_y = max(0, -self.offset.y)
999 term_x = max(0, -self.offset.x)
1000 map_y = max(0, self.offset.y)
1001 map_x = max(0, self.offset.x)
1002 while term_y < self.size.y and map_y < len(self.map_lines):
1003 to_draw = self.map_lines[map_y][map_x:self.left_window_width + self.offset.x]
1004 self.addstr(term_y, term_x, to_draw)
1008 def draw_names(self):
1009 players = [t for t in self.game.things if t.type_ == 'Player']
1010 players.sort(key=lambda t: len(t.name))
1012 shrink_offset = max(0, (self.size.y - self.left_window_width // 2) // 2)
1015 offset_y = y - shrink_offset
1016 max_len = max(5, (self.left_window_width // 2) - (offset_y * 2) - 8)
1018 if len(name) > max_len:
1019 name = name[:max_len - 1] + '…'
1020 self.addstr(y, 0, '@%s:%s' % (t.thing_char, name))
1022 if y >= self.size.y:
1025 def draw_face_popup(self):
1026 t = self.game.get_thing(self.draw_face)
1027 if not t or not hasattr(t, 'face'):
1028 self.draw_face = False
1031 start_x = self.left_window_width - 10
1032 def draw_body_part(body_part, end_y):
1033 self.addstr(end_y - 3, start_x, '----------')
1034 self.addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |')
1035 self.addstr(end_y - 1, start_x, '| ' + body_part[6:12] + ' |')
1036 self.addstr(end_y, start_x, '| ' + body_part[12:18] + ' |')
1038 if hasattr(t, 'face'):
1039 draw_body_part(t.face, self.size.y - 3)
1040 if hasattr(t, 'hat'):
1041 draw_body_part(t.hat, self.size.y - 6)
1042 self.addstr(self.size.y - 2, start_x, '----------')
1045 name = name[:6 - 1] + '…'
1046 self.addstr(self.size.y - 1, start_x, '@%s:%s' % (t.thing_char, name))
1048 def draw_help(self):
1049 content = "%s help\n\n%s\n\n" % (self.mode.short_desc,
1050 self.mode.help_intro)
1051 if len(self.mode.available_actions) > 0:
1052 content += "Available actions:\n"
1053 for action in self.mode.available_actions:
1054 if action in self.action_tasks:
1055 if self.action_tasks[action] not in self.game.tasks:
1057 if action == 'move_explorer':
1059 if action == 'move':
1060 key = ','.join(self.movement_keys)
1062 key = self.keys[action]
1063 content += '[%s] – %s\n' % (key, self.action_descriptions[action])
1065 content += self.mode.list_available_modes(self)
1066 for i in range(self.size.y):
1068 self.left_window_width * (not self.mode.has_input_prompt),
1069 ' ' * self.left_window_width)
1071 for line in content.split('\n'):
1072 lines += msg_into_lines_of_width(line, self.right_window_width)
1073 for i in range(len(lines)):
1074 if i >= self.size.y:
1077 self.left_window_width * (not self.mode.has_input_prompt),
1080 def draw_screen(self):
1081 self.stdscr.bkgd(' ', curses.color_pair(1))
1082 self.recalc_input_lines()
1083 if self.mode.has_input_prompt:
1085 if self.mode.shows_info:
1090 if not self.mode.is_intro:
1095 if self.mode.name in {'chat', 'play'}:
1098 self.draw_face_popup()
1100 def handle_server_message(self, msg):
1101 command, args = self.game.parser.parse(msg)
1104 def on_each_loop_start(self):
1105 prev_disconnected = self.socket.disconnected
1106 self.socket.keep_connection_alive()
1107 if prev_disconnected and not self.socket.disconnected:
1108 self.update_on_connect()
1113 def on_key(self, key, keycode):
1115 def task_action_on(action):
1116 return self.action_tasks[action] in self.game.tasks
1118 def move_explorer(direction):
1119 target = self.game.map_geometry.move_yx(self.explorer, direction)
1121 self.info_cached = None
1122 self.explorer = target
1124 self.send_tile_control_command()
1127 def pick_selectable(task_name):
1129 i = int(self.input_)
1130 if i < 0 or i >= len(self.selectables):
1131 self.log('? invalid index, aborted')
1133 self.send('TASK:%s %s' % (task_name, self.selectables[i]))
1135 self.log('? invalid index, aborted')
1137 self.switch_mode('play')
1139 def enter_ascii_art(command, height, width,
1140 with_pw=False, with_size=False):
1141 if with_size and self.ascii_draw_stage == 0:
1142 width = len(self.input_)
1144 self.log('? input too long, must be max 36; try again')
1145 # TODO: move max width mechanism server-side
1147 old_size = self.game.player.carrying.design[0]
1148 if width != old_size.x:
1149 # TODO: save remaining design?
1150 self.game.player.carrying.design[1] = ''
1151 self.game.player.carrying.design[0] = YX(old_size.y, width)
1152 elif len(self.input_) > width:
1153 self.log('? input too long, '
1154 'must be max %s; try again' % width)
1156 self.log(' ' + self.input_)
1157 if with_size and self.input_ in {'', ' '}\
1158 and self.ascii_draw_stage > 0:
1159 height = self.ascii_draw_stage
1162 height = self.ascii_draw_stage + 2
1163 if len(self.input_) < width:
1164 self.input_ += ' ' * (width - len(self.input_))
1165 self.full_ascii_draw += self.input_
1167 old_size = self.game.player.carrying.design[0]
1168 self.game.player.carrying.design[0] = YX(height, old_size.x)
1169 self.ascii_draw_stage += 1
1170 if self.ascii_draw_stage < height:
1171 self.restore_input_values()
1173 if with_pw and with_size:
1174 self.send('%s_SIZE %s %s' % (command, YX(height, width),
1175 quote(self.password)))
1177 self.send('%s %s %s' % (command, quote(self.full_ascii_draw),
1178 quote(self.password)))
1180 self.send('%s %s' % (command, quote(self.full_ascii_draw)))
1181 self.full_ascii_draw = ""
1182 self.ascii_draw_stage = 0
1184 self.switch_mode('edit')
1186 self.show_help = False
1187 self.draw_face = False
1188 if key == 'KEY_RESIZE':
1190 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
1191 self.input_ = self.input_[:-1]
1192 elif (((not self.mode.is_intro) and keycode == 27) # Escape
1193 or (self.mode.has_input_prompt and key == '\n'
1194 and self.input_ == ''\
1195 and self.mode.name in {'chat', 'command_thing',
1196 'take_thing', 'drop_thing',
1198 if self.mode.name not in {'chat', 'play', 'study', 'edit'}:
1199 self.log('@ aborted')
1200 self.switch_mode('play')
1201 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
1202 self.show_help = True
1204 self.restore_input_values()
1205 elif self.mode.has_input_prompt and key != '\n': # Return key
1207 max_length = self.right_window_width * self.size.y - len(self.input_prompt) - 1
1208 if len(self.input_) > max_length:
1209 self.input_ = self.input_[:max_length]
1210 elif key == self.keys['help'] and not self.mode.is_single_char_entry:
1211 self.show_help = True
1212 elif self.mode.name == 'login' and key == '\n':
1213 self.login_name = self.input_
1214 self.send('LOGIN ' + quote(self.input_))
1216 elif self.mode.name == 'enter_face' and key == '\n':
1217 enter_ascii_art('PLAYER_FACE', 3, 6)
1218 elif self.mode.name == 'enter_design' and key == '\n':
1219 if self.game.player.carrying.type_ == 'Hat':
1220 enter_ascii_art('THING_DESIGN',
1221 self.game.player.carrying.design[0].y,
1222 self.game.player.carrying.design[0].x, True)
1224 enter_ascii_art('THING_DESIGN',
1225 self.game.player.carrying.design[0].y,
1226 self.game.player.carrying.design[0].x,
1228 elif self.mode.name == 'take_thing' and key == '\n':
1229 pick_selectable('PICK_UP')
1230 elif self.mode.name == 'drop_thing' and key == '\n':
1231 pick_selectable('DROP')
1232 elif self.mode.name == 'command_thing' and key == '\n':
1233 self.send('TASK:COMMAND ' + quote(self.input_))
1235 elif self.mode.name == 'control_pw_pw' and key == '\n':
1236 if self.input_ == '':
1237 self.log('@ aborted')
1239 self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
1240 self.log('@ sent new password for protection character "%s"' % self.tile_control_char)
1241 self.switch_mode('admin')
1242 elif self.mode.name == 'password' and key == '\n':
1243 if self.input_ == '':
1245 self.password = self.input_
1246 self.switch_mode('edit')
1247 elif self.mode.name == 'admin_enter' and key == '\n':
1248 self.send('BECOME_ADMIN ' + quote(self.input_))
1249 self.switch_mode('play')
1250 elif self.mode.name == 'control_pw_type' and key == '\n':
1251 if len(self.input_) != 1:
1252 self.log('@ entered non-single-char, therefore aborted')
1253 self.switch_mode('admin')
1255 self.tile_control_char = self.input_
1256 self.switch_mode('control_pw_pw')
1257 elif self.mode.name == 'admin_thing_protect' and key == '\n':
1258 if len(self.input_) != 1:
1259 self.log('@ entered non-single-char, therefore aborted')
1261 self.send('THING_PROTECTION %s' % (quote(self.input_)))
1262 self.log('@ sent new protection character for thing')
1263 self.switch_mode('admin')
1264 elif self.mode.name == 'control_tile_type' and key == '\n':
1265 if len(self.input_) != 1:
1266 self.log('@ entered non-single-char, therefore aborted')
1267 self.switch_mode('admin')
1269 self.tile_control_char = self.input_
1270 self.switch_mode('control_tile_draw')
1271 elif self.mode.name == 'chat' and key == '\n':
1272 if self.input_ == '':
1274 if self.input_[0] == '/':
1275 if self.input_.startswith('/nick'):
1276 tokens = self.input_.split(maxsplit=1)
1277 if len(tokens) == 2:
1278 self.send('NICK ' + quote(tokens[1]))
1280 self.log('? need login name')
1282 self.log('? unknown command')
1284 self.send('ALL ' + quote(self.input_))
1286 elif self.mode.name == 'name_thing' and key == '\n':
1287 if self.input_ == '':
1289 self.send('THING_NAME %s %s' % (quote(self.input_),
1290 quote(self.password)))
1291 self.switch_mode('edit')
1292 elif self.mode.name == 'annotate' and key == '\n':
1293 if self.input_ == '':
1295 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
1296 quote(self.password)))
1297 self.switch_mode('edit')
1298 elif self.mode.name == 'portal' and key == '\n':
1299 if self.input_ == '':
1301 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
1302 quote(self.password)))
1303 self.switch_mode('edit')
1304 elif self.mode.name == 'study':
1305 if self.mode.mode_switch_on_key(self, key):
1307 elif key == self.keys['toggle_map_mode']:
1308 self.toggle_map_mode()
1309 elif key in self.movement_keys:
1310 move_explorer(self.movement_keys[key])
1311 elif self.mode.name == 'play':
1312 if self.mode.mode_switch_on_key(self, key):
1314 elif key == self.keys['door'] and task_action_on('door'):
1315 self.send('TASK:DOOR')
1316 elif key == self.keys['consume'] and task_action_on('consume'):
1317 self.send('TASK:INTOXICATE')
1318 elif key == self.keys['wear'] and task_action_on('wear'):
1319 self.send('TASK:WEAR')
1320 elif key == self.keys['spin'] and task_action_on('spin'):
1321 self.send('TASK:SPIN')
1322 elif key == self.keys['dance'] and task_action_on('dance'):
1323 self.send('TASK:DANCE')
1324 elif key == self.keys['teleport']:
1325 if self.game.player.position in self.game.portals:
1326 self.socket.host = self.game.portals[self.game.player.position]
1330 self.log('? not standing on portal')
1331 elif key in self.movement_keys and task_action_on('move'):
1332 self.send('TASK:MOVE ' + self.movement_keys[key])
1333 elif self.mode.name == 'write':
1334 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
1335 self.switch_mode('edit')
1336 elif self.mode.name == 'control_tile_draw':
1337 if self.mode.mode_switch_on_key(self, key):
1339 elif key in self.movement_keys:
1340 move_explorer(self.movement_keys[key])
1341 elif key == self.keys['toggle_tile_draw']:
1342 self.tile_draw = False if self.tile_draw else True
1343 elif self.mode.name == 'admin':
1344 if self.mode.mode_switch_on_key(self, key):
1346 elif key == self.keys['toggle_map_mode']:
1347 self.toggle_map_mode()
1348 elif key in self.movement_keys and task_action_on('move'):
1349 self.send('TASK:MOVE ' + self.movement_keys[key])
1350 elif self.mode.name == 'edit':
1351 if self.mode.mode_switch_on_key(self, key):
1353 elif key == self.keys['flatten'] and task_action_on('flatten'):
1354 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
1355 elif key == self.keys['install'] and task_action_on('install'):
1356 self.send('TASK:INSTALL %s' % quote(self.password))
1357 elif key == self.keys['toggle_map_mode']:
1358 self.toggle_map_mode()
1359 elif key in self.movement_keys and task_action_on('move'):
1360 self.send('TASK:MOVE ' + self.movement_keys[key])
1362 if len(sys.argv) != 2:
1363 raise ArgError('wrong number of arguments, need game host')