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, CursesScreen
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)
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):
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)
502 self.do_refresh = True
503 self.login_name = None
504 self.map_mode = 'terrain + things'
505 self.password = 'foo'
506 self.switch_mode('waiting_for_server')
508 'switch_to_chat': 't',
509 'switch_to_play': 'p',
510 'switch_to_password': 'P',
511 'switch_to_annotate': 'M',
512 'switch_to_portal': 'T',
513 'switch_to_study': '?',
514 'switch_to_edit': 'E',
515 'switch_to_write': 'm',
516 'switch_to_name_thing': 'N',
517 'switch_to_command_thing': 'O',
518 'switch_to_admin_enter': 'A',
519 'switch_to_control_pw_type': 'C',
520 'switch_to_control_tile_type': 'Q',
521 'switch_to_admin_thing_protect': 'T',
523 'switch_to_enter_face': 'f',
524 'switch_to_enter_design': 'D',
525 'switch_to_take_thing': 'z',
526 'switch_to_drop_thing': 'u',
535 'toggle_map_mode': 'L',
536 'toggle_tile_draw': 'm',
537 'hex_move_upleft': 'w',
538 'hex_move_upright': 'e',
539 'hex_move_right': 'd',
540 'hex_move_downright': 'x',
541 'hex_move_downleft': 'y',
542 'hex_move_left': 'a',
543 'square_move_up': 'w',
544 'square_move_left': 'a',
545 'square_move_down': 's',
546 'square_move_right': 'd',
548 if os.path.isfile('config.json'):
549 with open('config.json', 'r') as f:
550 keys_conf = json.loads(f.read())
552 self.keys[k] = keys_conf[k]
553 self.show_help = False
554 self.input_lines = []
558 self.ascii_draw_stage = 0
559 self.full_ascii_draw = ''
560 self.offset = YX(0,0)
561 self.screen = CursesScreen()
562 self.screen.wrap_loop(self.loop)
564 def update_on_connect(self):
565 self.socket.send('TASKS')
566 self.socket.send('TERRAINS')
567 self.socket.send('THING_TYPES')
568 self.switch_mode('login')
572 self.log_msg('@ attempting reconnect')
573 self.socket.send('QUIT')
574 # necessitated by some strange SSL race conditions with ws4py
575 time.sleep(0.1) # FIXME find out why exactly necessary
576 self.switch_mode('waiting_for_server')
577 self.socket.connect()
578 self.update_on_connect()
581 self.socket.send(msg)
582 if self.socket.disconnected:
583 self.do_refresh = True
585 def socket_log(self, msg):
586 self.log_msg('@ ' + msg)
588 def log_msg(self, msg):
590 if len(self.log) > 100:
591 self.log = self.log[-100:]
593 def restore_input_values(self):
594 if self.mode.name == 'annotate' and self.explorer in self.game.annotations:
595 self.input_ = self.game.annotations[self.explorer]
596 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
597 self.input_ = self.game.portals[self.explorer]
598 elif self.mode.name == 'password':
599 self.input_ = self.password
600 elif self.mode.name == 'name_thing':
601 if hasattr(self.game.player.carrying, 'name'):
602 self.input_ = self.game.player.carrying.name
603 elif self.mode.name == 'admin_thing_protect':
604 if hasattr(self.game.player.carrying, 'protection'):
605 self.input_ = self.game.player.carrying.protection
606 elif self.mode.name == 'enter_face':
607 start = self.ascii_draw_stage * 6
608 end = (self.ascii_draw_stage + 1) * 6
609 self.input_ = self.game.player.face[start:end]
610 elif self.mode.name == 'enter_design':
611 width = self.game.player.carrying.design[0].x
612 start = self.ascii_draw_stage * width
613 end = (self.ascii_draw_stage + 1) * width
614 self.input_ = self.game.player.carrying.design[1][start:end]
616 def send_tile_control_command(self):
617 self.send('SET_TILE_CONTROL %s %s' %
618 (self.explorer, quote(self.tile_control_char)))
620 def toggle_map_mode(self):
621 if self.map_mode == 'terrain only':
622 self.map_mode = 'terrain + annotations'
623 elif self.map_mode == 'terrain + annotations':
624 self.map_mode = 'terrain + things'
625 elif self.map_mode == 'terrain + things':
626 self.map_mode = 'protections'
627 elif self.map_mode == 'protections':
628 self.map_mode = 'terrain only'
630 def switch_mode(self, mode_name):
632 def fail(msg, return_mode='play'):
633 self.log_msg('? ' + msg)
635 self.switch_mode(return_mode)
637 if self.mode and self.mode.name == 'control_tile_draw':
638 self.log_msg('@ finished tile protection drawing.')
639 self.draw_face = False
640 self.tile_draw = False
641 self.ascii_draw_stage = 0
642 self.full_ascii_draw = ''
643 if mode_name == 'command_thing' and\
644 (not self.game.player.carrying or
645 not self.game.player.carrying.commandable):
646 return fail('not carrying anything commandable')
647 if mode_name == 'name_thing' and not self.game.player.carrying:
648 return fail('not carrying anything to re-name', 'edit')
649 if mode_name == 'admin_thing_protect' and not self.game.player.carrying:
650 return fail('not carrying anything to protect')
651 if mode_name == 'take_thing' and self.game.player.carrying:
652 return fail('already carrying something')
653 if mode_name == 'drop_thing' and not self.game.player.carrying:
654 return fail('not carrying anything droppable')
655 if mode_name == 'enter_design' and\
656 (not self.game.player.carrying or
657 not hasattr(self.game.player.carrying, 'design')):
658 return fail('not carrying designable to edit', 'edit')
659 if mode_name == 'admin_enter' and self.is_admin:
661 self.mode = getattr(self, 'mode_' + mode_name)
662 if self.mode.name in {'control_tile_draw', 'control_tile_type',
664 self.map_mode = 'protections'
665 elif self.mode.name != 'edit':
666 self.map_mode = 'terrain + things'
667 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
668 self.explorer = YX(self.game.player.position.y,
669 self.game.player.position.x)
670 if self.mode.is_single_char_entry:
671 self.show_help = True
672 if len(self.mode.intro_msg) > 0:
673 self.log_msg(self.mode.intro_msg)
674 if self.mode.name == 'login':
676 self.send('LOGIN ' + quote(self.login_name))
678 self.log_msg('@ enter username')
679 elif self.mode.name == 'take_thing':
680 self.log_msg('Portable things in reach for pick-up:')
682 'HERE': YX(0, 0), 'LEFT': YX(0, -1), 'RIGHT': YX(0, 1)
684 if type(self.game.map_geometry) == MapGeometrySquare:
685 directed_moves['UP'] = YX(-1, 0)
686 directed_moves['DOWN'] = YX(1, 0)
687 elif type(self.game.map_geometry) == MapGeometryHex:
688 if self.game.player.position.y % 2:
689 directed_moves['UPLEFT'] = YX(-1, 0)
690 directed_moves['UPRIGHT'] = YX(-1, 1)
691 directed_moves['DOWNLEFT'] = YX(1, 0)
692 directed_moves['DOWNRIGHT'] = YX(1, 1)
694 directed_moves['UPLEFT'] = YX(-1, -1)
695 directed_moves['UPRIGHT'] = YX(-1, 0)
696 directed_moves['DOWNLEFT'] = YX(1, -1)
697 directed_moves['DOWNRIGHT'] = YX(1, 0)
699 for direction in directed_moves:
700 move = directed_moves[direction]
701 select_range[direction] = self.game.player.position + move
702 self.selectables = []
704 for direction in select_range:
705 for t in [t for t in self.game.things
706 if t.portable and t.position == select_range[direction]]:
707 self.selectables += [t.id_]
708 directions += [direction]
709 if len(self.selectables) == 0:
710 return fail('nothing to pick-up')
712 for i in range(len(self.selectables)):
713 t = self.game.get_thing(self.selectables[i])
714 self.log_msg('%s %s: %s' % (i, directions[i],
715 self.get_thing_info(t)))
716 elif self.mode.name == 'drop_thing':
717 self.log_msg('Direction to drop thing to:')
719 ['HERE'] + list(self.game.tui.movement_keys.values())
720 for i in range(len(self.selectables)):
721 self.log_msg(str(i) + ': ' + self.selectables[i])
722 elif self.mode.name == 'enter_design':
723 if self.game.player.carrying.type_ == 'Hat':
724 self.log_msg('@ The design you enter must be %s lines of max %s '
725 'characters width each'
726 % (self.game.player.carrying.design[0].y,
727 self.game.player.carrying.design[0].x))
728 self.log_msg('@ Legal characters: ' + self.game.players_hat_chars)
729 self.log_msg('@ (Eat cookies to extend the ASCII characters available for drawing.)')
731 self.log_msg('@ Width of first line determines maximum width for remaining design')
732 self.log_msg('@ Finish design by entering an empty line (multiple space characters do not count as empty)')
733 elif self.mode.name == 'command_thing':
734 self.send('TASK:COMMAND ' + quote('HELP'))
735 elif self.mode.name == 'control_pw_pw':
736 self.log_msg('@ enter protection password for "%s":' % self.tile_control_char)
737 elif self.mode.name == 'control_tile_draw':
738 self.log_msg('@ 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']))
740 self.restore_input_values()
742 def set_default_colors(self):
743 if curses.can_change_color():
744 curses.init_color(7, 1000, 1000, 1000)
745 curses.init_color(0, 0, 0, 0)
746 self.do_refresh = True
748 def set_random_colors(self):
752 return int(offset + random.random()*375)
754 if curses.can_change_color():
755 curses.init_color(7, rand(625), rand(625), rand(625))
756 curses.init_color(0, rand(0), rand(0), rand(0))
757 self.do_refresh = True
761 return self.info_cached
762 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
764 if len(self.game.fov) > pos_i and self.game.fov[pos_i] != '.':
765 info_to_cache += 'outside field of view'
767 for t in self.game.things:
768 if t.position == self.explorer:
769 info_to_cache += '%s' % self.get_thing_info(t, True)
770 terrain_char = self.game.map_content[pos_i]
772 if terrain_char in self.game.terrains:
773 terrain_desc = self.game.terrains[terrain_char]
774 info_to_cache += 'TERRAIN: %s (%s' % (terrain_char,
776 protection = self.game.map_control_content[pos_i]
777 if protection != '.':
778 info_to_cache += '/protection:%s' % protection
779 info_to_cache += ')\n'
780 if self.explorer in self.game.portals:
781 info_to_cache += 'PORTAL: ' +\
782 self.game.portals[self.explorer] + '\n'
783 if self.explorer in self.game.annotations:
784 info_to_cache += 'ANNOTATION: ' +\
785 self.game.annotations[self.explorer]
786 self.info_cached = info_to_cache
787 return self.info_cached
789 def get_thing_info(self, t, detailed=False):
793 info += self.game.thing_types[t.type_]
794 if hasattr(t, 'thing_char'):
796 if hasattr(t, 'name'):
797 info += ': %s' % t.name
798 info += ' (%s' % t.type_
799 if hasattr(t, 'installed'):
801 if t.type_ == 'Bottle':
802 if t.thing_char == '_':
804 elif t.thing_char == '~':
807 protection = t.protection
808 if protection != '.':
809 info += '/protection:%s' % protection
811 if hasattr(t, 'hat') or hasattr(t, 'face'):
812 info += '----------\n'
813 if hasattr(t, 'hat'):
814 info += '| %s |\n' % t.hat[0:6]
815 info += '| %s |\n' % t.hat[6:12]
816 info += '| %s |\n' % t.hat[12:18]
817 if hasattr(t, 'face'):
818 info += '| %s |\n' % t.face[0:6]
819 info += '| %s |\n' % t.face[6:12]
820 info += '| %s |\n' % t.face[12:18]
821 info += '----------\n'
822 if hasattr(t, 'design'):
823 line_length = t.design[0].x
825 for i in range(t.design[0].y):
826 start = i * line_length
827 end = (i + 1) * line_length
828 lines += [t.design[1][start:end]]
829 info += '-' * (line_length + 4) + '\n'
831 info += '| %s |\n' % line
832 info += '-' * (line_length + 4) + '\n'
839 def safe_addstr(y, x, line):
840 self.screen.safe_addstr(y, x, line, curses.color_pair(1))
842 def handle_input(msg):
843 command, args = self.parser.parse(msg)
846 def task_action_on(action):
847 return action_tasks[action] in self.game.tasks
849 def reset_screen_size():
850 self.screen.reset_size()
851 self.left_window_width = min(52, int(self.screen.size.x / 2))
852 self.right_window_width = self.screen.size.x - self.left_window_width
854 def recalc_input_lines():
855 if not self.mode.has_input_prompt:
856 self.input_lines = []
858 self.input_lines = msg_into_lines_of_width(input_prompt
860 self.right_window_width)
862 def move_explorer(direction):
863 target = self.game.map_geometry.move_yx(self.explorer, direction)
865 self.info_cached = None
866 self.explorer = target
868 self.send_tile_control_command()
874 for line in self.log:
875 lines += msg_into_lines_of_width(line, self.right_window_width)
878 max_y = self.screen.size.y - len(self.input_lines)
879 for i in range(len(lines)):
880 if (i >= max_y - height_header):
882 safe_addstr(max_y - i - 1, self.left_window_width, lines[i])
885 info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info())
886 lines = msg_into_lines_of_width(info, self.right_window_width)
888 for i in range(len(lines)):
889 y = height_header + i
890 if y >= self.screen.size.y - len(self.input_lines):
892 safe_addstr(y, self.left_window_width, lines[i])
895 y = self.screen.size.y - len(self.input_lines)
896 for i in range(len(self.input_lines)):
897 safe_addstr(y, self.left_window_width, self.input_lines[i])
901 stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy,
902 self.game.bladder_pressure)
903 safe_addstr(0, self.left_window_width, stats)
906 help = "hit [%s] for help" % self.keys['help']
907 if self.mode.has_input_prompt:
908 help = "enter /help for help"
909 safe_addstr(1, self.left_window_width,
910 'MODE: %s – %s' % (self.mode.short_desc, help))
913 if (not self.game.turn_complete) and len(self.map_lines) == 0:
915 if self.game.turn_complete:
917 for y in range(self.game.map_geometry.size.y):
918 start = self.game.map_geometry.size.x * y
919 end = start + self.game.map_geometry.size.x
920 if self.map_mode == 'protections':
921 map_lines_split += [[c + ' ' for c
922 in self.game.map_control_content[start:end]]]
924 map_lines_split += [[c + ' ' for c
925 in self.game.map_content[start:end]]]
926 if self.map_mode == 'terrain + annotations':
927 for p in self.game.annotations:
928 map_lines_split[p.y][p.x] = 'A '
929 elif self.map_mode == 'terrain + things':
930 for p in self.game.portals.keys():
931 original = map_lines_split[p.y][p.x]
932 map_lines_split[p.y][p.x] = original[0] + 'P'
935 def draw_thing(t, used_positions):
936 symbol = self.game.thing_types[t.type_]
938 if hasattr(t, 'thing_char'):
939 meta_char = t.thing_char
940 if t.position in used_positions:
942 if hasattr(t, 'carrying') and t.carrying:
944 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
945 used_positions += [t.position]
947 for t in [t for t in self.game.things if t.type_ != 'Player']:
948 draw_thing(t, used_positions)
949 for t in [t for t in self.game.things if t.type_ == 'Player']:
950 draw_thing(t, used_positions)
951 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
952 map_lines_split[self.explorer.y][self.explorer.x] = '??'
953 elif self.map_mode != 'terrain + things':
954 map_lines_split[self.game.player.position.y]\
955 [self.game.player.position.x] = '??'
957 if type(self.game.map_geometry) == MapGeometryHex:
959 for line in map_lines_split:
960 self.map_lines += [indent * ' ' + ''.join(line)]
961 indent = 0 if indent else 1
963 for line in map_lines_split:
964 self.map_lines += [''.join(line)]
965 window_center = YX(int(self.screen.size.y / 2),
966 int(self.left_window_width / 2))
967 center = self.game.player.position
968 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
969 center = self.explorer
970 center = YX(center.y, center.x * 2)
971 self.offset = center - window_center
972 if type(self.game.map_geometry) == MapGeometryHex and self.offset.y % 2:
973 self.offset += YX(0, 1)
974 term_y = max(0, -self.offset.y)
975 term_x = max(0, -self.offset.x)
976 map_y = max(0, self.offset.y)
977 map_x = max(0, self.offset.x)
978 while term_y < self.screen.size.y and map_y < len(self.map_lines):
979 to_draw = self.map_lines[map_y][map_x:self.left_window_width + self.offset.x]
980 safe_addstr(term_y, term_x, to_draw)
985 players = [t for t in self.game.things if t.type_ == 'Player']
986 players.sort(key=lambda t: len(t.name))
988 shrink_offset = max(0, (self.screen.size.y - self.left_window_width // 2) // 2)
991 offset_y = y - shrink_offset
992 max_len = max(5, (self.left_window_width // 2) - (offset_y * 2) - 8)
994 if len(name) > max_len:
995 name = name[:max_len - 1] + '…'
996 safe_addstr(y, 0, '@%s:%s' % (t.thing_char, name))
998 if y >= self.screen.size.y:
1001 def draw_face_popup():
1002 t = self.game.get_thing(self.draw_face)
1003 if not t or not hasattr(t, 'face'):
1004 self.draw_face = False
1007 start_x = self.left_window_width - 10
1008 def draw_body_part(body_part, end_y):
1009 safe_addstr(end_y - 3, start_x, '----------')
1010 safe_addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |')
1011 safe_addstr(end_y - 1, start_x, '| ' + body_part[6:12] + ' |')
1012 safe_addstr(end_y, start_x, '| ' + body_part[12:18] + ' |')
1014 if hasattr(t, 'face'):
1015 draw_body_part(t.face, self.screen.size.y - 3)
1016 if hasattr(t, 'hat'):
1017 draw_body_part(t.hat, self.screen.size.y - 6)
1018 safe_addstr(self.screen.size.y - 2, start_x, '----------')
1021 name = name[:6 - 1] + '…'
1022 safe_addstr(self.screen.size.y - 1, start_x,
1023 '@%s:%s' % (t.thing_char, name))
1026 content = "%s help\n\n%s\n\n" % (self.mode.short_desc,
1027 self.mode.help_intro)
1028 if len(self.mode.available_actions) > 0:
1029 content += "Available actions:\n"
1030 for action in self.mode.available_actions:
1031 if action in action_tasks:
1032 if action_tasks[action] not in self.game.tasks:
1034 if action == 'move_explorer':
1036 if action == 'move':
1037 key = ','.join(self.movement_keys)
1039 key = self.keys[action]
1040 content += '[%s] – %s\n' % (key, action_descriptions[action])
1042 content += self.mode.list_available_modes(self)
1043 for i in range(self.screen.size.y):
1045 self.left_window_width * (not self.mode.has_input_prompt),
1046 ' ' * self.left_window_width)
1048 for line in content.split('\n'):
1049 lines += msg_into_lines_of_width(line, self.right_window_width)
1050 for i in range(len(lines)):
1051 if i >= self.screen.size.y:
1054 self.left_window_width * (not self.mode.has_input_prompt),
1058 self.screen.stdscr.clear()
1059 self.screen.stdscr.bkgd(' ', curses.color_pair(1))
1060 recalc_input_lines()
1061 if self.mode.has_input_prompt:
1063 if self.mode.shows_info:
1068 if not self.mode.is_intro:
1073 if self.mode.name in {'chat', 'play'}:
1078 def pick_selectable(task_name):
1080 i = int(self.input_)
1081 if i < 0 or i >= len(self.selectables):
1082 self.log_msg('? invalid index, aborted')
1084 self.send('TASK:%s %s' % (task_name, self.selectables[i]))
1086 self.log_msg('? invalid index, aborted')
1088 self.switch_mode('play')
1090 def enter_ascii_art(command, height, width,
1091 with_pw=False, with_size=False):
1092 if with_size and self.ascii_draw_stage == 0:
1093 width = len(self.input_)
1095 self.log_msg('? input too long, must be max 36; try again')
1096 # TODO: move max width mechanism server-side
1098 old_size = self.game.player.carrying.design[0]
1099 if width != old_size.x:
1100 # TODO: save remaining design?
1101 self.game.player.carrying.design[1] = ''
1102 self.game.player.carrying.design[0] = YX(old_size.y, width)
1103 elif len(self.input_) > width:
1104 self.log_msg('? input too long, '
1105 'must be max %s; try again' % width)
1107 self.log_msg(' ' + self.input_)
1108 if with_size and self.input_ in {'', ' '}\
1109 and self.ascii_draw_stage > 0:
1110 height = self.ascii_draw_stage
1113 height = self.ascii_draw_stage + 2
1114 if len(self.input_) < width:
1115 self.input_ += ' ' * (width - len(self.input_))
1116 self.full_ascii_draw += self.input_
1118 old_size = self.game.player.carrying.design[0]
1119 self.game.player.carrying.design[0] = YX(height, old_size.x)
1120 self.ascii_draw_stage += 1
1121 if self.ascii_draw_stage < height:
1122 self.restore_input_values()
1124 if with_pw and with_size:
1125 self.send('%s_SIZE %s %s' % (command, YX(height, width),
1126 quote(self.password)))
1128 self.send('%s %s %s' % (command, quote(self.full_ascii_draw),
1129 quote(self.password)))
1131 self.send('%s %s' % (command, quote(self.full_ascii_draw)))
1132 self.full_ascii_draw = ""
1133 self.ascii_draw_stage = 0
1135 self.switch_mode('edit')
1137 action_descriptions = {
1139 'flatten': 'flatten surroundings',
1140 'teleport': 'teleport',
1141 'take_thing': 'pick up thing',
1142 'drop_thing': 'drop thing',
1143 'toggle_map_mode': 'toggle map view',
1144 'toggle_tile_draw': 'toggle protection character drawing',
1145 'install': '(un-)install',
1146 'wear': '(un-)wear',
1147 'door': 'open/close',
1148 'consume': 'consume',
1154 'flatten': 'FLATTEN_SURROUNDINGS',
1155 'take_thing': 'PICK_UP',
1156 'drop_thing': 'DROP',
1158 'install': 'INSTALL',
1161 'command': 'COMMAND',
1162 'consume': 'INTOXICATE',
1167 curses.start_color()
1168 self.set_default_colors()
1169 curses.init_pair(1, 7, 0)
1170 if not curses.can_change_color():
1171 self.log_msg('@ unfortunately, your terminal does not seem to '
1172 'support re-definition of colors; you might miss out '
1173 'on some color effects')
1175 self.explorer = YX(0, 0)
1177 store_widechar = False
1180 prev_disconnected = self.socket.disconnected
1181 self.socket.keep_connection_alive()
1182 if prev_disconnected and not self.socket.disconnected:
1183 self.update_on_connect()
1189 self.do_refresh = False
1190 for msg in self.socket.get_message():
1193 key = self.screen.stdscr.getkey()
1194 self.do_refresh = True
1195 except curses.error:
1200 # workaround for <https://stackoverflow.com/a/56390915>
1202 store_widechar = False
1203 key = bytes([195, keycode]).decode()
1205 store_widechar = True
1207 self.show_help = False
1208 self.draw_face = False
1209 if key == 'KEY_RESIZE':
1211 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
1212 self.input_ = self.input_[:-1]
1213 elif (((not self.mode.is_intro) and keycode == 27) # Escape
1214 or (self.mode.has_input_prompt and key == '\n'
1215 and self.input_ == ''\
1216 and self.mode.name in {'chat', 'command_thing',
1217 'take_thing', 'drop_thing',
1219 if self.mode.name not in {'chat', 'play', 'study', 'edit'}:
1220 self.log_msg('@ aborted')
1221 self.switch_mode('play')
1222 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
1223 self.show_help = True
1225 self.restore_input_values()
1226 elif self.mode.has_input_prompt and key != '\n': # Return key
1228 max_length = self.right_window_width * self.screen.size.y - len(input_prompt) - 1
1229 if len(self.input_) > max_length:
1230 self.input_ = self.input_[:max_length]
1231 elif key == self.keys['help'] and not self.mode.is_single_char_entry:
1232 self.show_help = True
1233 elif self.mode.name == 'login' and key == '\n':
1234 self.login_name = self.input_
1235 self.send('LOGIN ' + quote(self.input_))
1237 elif self.mode.name == 'enter_face' and key == '\n':
1238 enter_ascii_art('PLAYER_FACE', 3, 6)
1239 elif self.mode.name == 'enter_design' and key == '\n':
1240 if self.game.player.carrying.type_ == 'Hat':
1241 enter_ascii_art('THING_DESIGN',
1242 self.game.player.carrying.design[0].y,
1243 self.game.player.carrying.design[0].x, True)
1245 enter_ascii_art('THING_DESIGN',
1246 self.game.player.carrying.design[0].y,
1247 self.game.player.carrying.design[0].x,
1249 elif self.mode.name == 'take_thing' and key == '\n':
1250 pick_selectable('PICK_UP')
1251 elif self.mode.name == 'drop_thing' and key == '\n':
1252 pick_selectable('DROP')
1253 elif self.mode.name == 'command_thing' and key == '\n':
1254 self.send('TASK:COMMAND ' + quote(self.input_))
1256 elif self.mode.name == 'control_pw_pw' and key == '\n':
1257 if self.input_ == '':
1258 self.log_msg('@ aborted')
1260 self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
1261 self.log_msg('@ sent new password for protection character "%s"' % self.tile_control_char)
1262 self.switch_mode('admin')
1263 elif self.mode.name == 'password' and key == '\n':
1264 if self.input_ == '':
1266 self.password = self.input_
1267 self.switch_mode('edit')
1268 elif self.mode.name == 'admin_enter' and key == '\n':
1269 self.send('BECOME_ADMIN ' + quote(self.input_))
1270 self.switch_mode('play')
1271 elif self.mode.name == 'control_pw_type' and key == '\n':
1272 if len(self.input_) != 1:
1273 self.log_msg('@ entered non-single-char, therefore aborted')
1274 self.switch_mode('admin')
1276 self.tile_control_char = self.input_
1277 self.switch_mode('control_pw_pw')
1278 elif self.mode.name == 'admin_thing_protect' and key == '\n':
1279 if len(self.input_) != 1:
1280 self.log_msg('@ entered non-single-char, therefore aborted')
1282 self.send('THING_PROTECTION %s' % (quote(self.input_)))
1283 self.log_msg('@ sent new protection character for thing')
1284 self.switch_mode('admin')
1285 elif self.mode.name == 'control_tile_type' and key == '\n':
1286 if len(self.input_) != 1:
1287 self.log_msg('@ entered non-single-char, therefore aborted')
1288 self.switch_mode('admin')
1290 self.tile_control_char = self.input_
1291 self.switch_mode('control_tile_draw')
1292 elif self.mode.name == 'chat' and key == '\n':
1293 if self.input_ == '':
1295 if self.input_[0] == '/':
1296 if self.input_.startswith('/nick'):
1297 tokens = self.input_.split(maxsplit=1)
1298 if len(tokens) == 2:
1299 self.send('NICK ' + quote(tokens[1]))
1301 self.log_msg('? need login name')
1303 self.log_msg('? unknown command')
1305 self.send('ALL ' + quote(self.input_))
1307 elif self.mode.name == 'name_thing' and key == '\n':
1308 if self.input_ == '':
1310 self.send('THING_NAME %s %s' % (quote(self.input_),
1311 quote(self.password)))
1312 self.switch_mode('edit')
1313 elif self.mode.name == 'annotate' and key == '\n':
1314 if self.input_ == '':
1316 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
1317 quote(self.password)))
1318 self.switch_mode('edit')
1319 elif self.mode.name == 'portal' and key == '\n':
1320 if self.input_ == '':
1322 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
1323 quote(self.password)))
1324 self.switch_mode('edit')
1325 elif self.mode.name == 'study':
1326 if self.mode.mode_switch_on_key(self, key):
1328 elif key == self.keys['toggle_map_mode']:
1329 self.toggle_map_mode()
1330 elif key in self.movement_keys:
1331 move_explorer(self.movement_keys[key])
1332 elif self.mode.name == 'play':
1333 if self.mode.mode_switch_on_key(self, key):
1335 elif key == self.keys['door'] and task_action_on('door'):
1336 self.send('TASK:DOOR')
1337 elif key == self.keys['consume'] and task_action_on('consume'):
1338 self.send('TASK:INTOXICATE')
1339 elif key == self.keys['wear'] and task_action_on('wear'):
1340 self.send('TASK:WEAR')
1341 elif key == self.keys['spin'] and task_action_on('spin'):
1342 self.send('TASK:SPIN')
1343 elif key == self.keys['dance'] and task_action_on('dance'):
1344 self.send('TASK:DANCE')
1345 elif key == self.keys['teleport']:
1346 if self.game.player.position in self.game.portals:
1347 self.socket.host = self.game.portals[self.game.player.position]
1351 self.log_msg('? not standing on portal')
1352 elif key in self.movement_keys and task_action_on('move'):
1353 self.send('TASK:MOVE ' + self.movement_keys[key])
1354 elif self.mode.name == 'write':
1355 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
1356 self.switch_mode('edit')
1357 elif self.mode.name == 'control_tile_draw':
1358 if self.mode.mode_switch_on_key(self, key):
1360 elif key in self.movement_keys:
1361 move_explorer(self.movement_keys[key])
1362 elif key == self.keys['toggle_tile_draw']:
1363 self.tile_draw = False if self.tile_draw else True
1364 elif self.mode.name == 'admin':
1365 if self.mode.mode_switch_on_key(self, key):
1367 elif key == self.keys['toggle_map_mode']:
1368 self.toggle_map_mode()
1369 elif key in self.movement_keys and task_action_on('move'):
1370 self.send('TASK:MOVE ' + self.movement_keys[key])
1371 elif self.mode.name == 'edit':
1372 if self.mode.mode_switch_on_key(self, key):
1374 elif key == self.keys['flatten'] and task_action_on('flatten'):
1375 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
1376 elif key == self.keys['install'] and task_action_on('install'):
1377 self.send('TASK:INSTALL %s' % quote(self.password))
1378 elif key == self.keys['toggle_map_mode']:
1379 self.toggle_map_mode()
1380 elif key in self.movement_keys and task_action_on('move'):
1381 self.send('TASK:MOVE ' + self.movement_keys[key])
1383 if len(sys.argv) != 2:
1384 raise ArgError('wrong number of arguments, need game host')