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
18 'long': 'This mode allows you to interact with the map in various ways.'
23 '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.'},
25 'short': 'world edit',
27 '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.'
30 'short': 'name thing',
32 'long': 'Give name to/change name of carried thing.'
37 'long': 'Enter a command to the thing you carry. Enter nothing to return to play mode.'
41 'intro': 'Pick up a thing in reach by entering its index number. Enter nothing to abort.',
42 'long': 'You see a list of things which you could pick up. Enter the target thing\'s index, or, to leave, nothing.'
46 'intro': 'Enter number of direction to which you want to drop thing.',
47 'long': 'Drop currently carried thing by entering the target direction index. Enter nothing to return to play mode..'
49 'admin_thing_protect': {
50 'short': 'change thing protection',
51 'intro': '@ enter thing protection character:',
52 'long': 'Change protection character for carried thing.'
56 'intro': '@ enter face line:',
57 '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..'
60 'short': 'edit design',
61 'intro': '@ enter design:',
62 'long': 'Enter design for carried thing as ASCII art.'
67 '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.'
70 'short': 'change protection character password',
71 'intro': '@ enter protection character for which you want to change the password:',
72 '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.'
75 'short': 'change protection character password',
77 '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.'
79 'control_tile_type': {
80 'short': 'change tiles protection',
81 'intro': '@ enter protection character which you want to draw:',
82 '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.'
84 'control_tile_draw': {
85 'short': 'change tiles protection',
87 '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.'
90 'short': 'annotate tile',
92 '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.'
95 'short': 'edit portal',
97 '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.'
102 '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'
107 'long': 'Enter your player name.'
109 'waiting_for_server': {
110 'short': 'waiting for server response',
111 'intro': '@ waiting for server …',
112 'long': 'Waiting for a server response.'
115 'short': 'waiting for server response',
117 'long': 'Waiting for a server response.'
120 'short': 'set world edit password',
122 '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.'
125 'short': 'become admin',
126 'intro': '@ enter admin password:',
127 'long': 'This mode allows you to become admin if you know an admin password.'
132 'long': 'This mode allows you access to actions limited to administrators.'
136 def cmd_TURN(game, n):
137 game.turn_complete = False
138 cmd_TURN.argtypes = 'int:nonneg'
140 def cmd_OTHER_WIPE(game):
141 game.portals_new = {}
142 game.annotations_new = {}
144 cmd_OTHER_WIPE.argtypes = ''
146 def cmd_LOGIN_OK(game):
147 game.tui.switch_mode('post_login_wait')
148 game.tui.send('GET_GAMESTATE')
149 game.tui.log_msg('@ welcome!')
150 cmd_LOGIN_OK.argtypes = ''
152 def cmd_ADMIN_OK(game):
153 game.tui.is_admin = True
154 game.tui.log_msg('@ you now have admin rights')
155 game.tui.switch_mode('admin')
156 game.tui.do_refresh = True
157 cmd_ADMIN_OK.argtypes = ''
159 def cmd_REPLY(game, msg):
160 game.tui.log_msg('#MUSICPLAYER: ' + msg)
161 game.tui.do_refresh = True
162 cmd_REPLY.argtypes = 'string'
164 def cmd_CHAT(game, msg):
165 game.tui.log_msg('# ' + msg)
166 game.tui.do_refresh = True
167 cmd_CHAT.argtypes = 'string'
169 def cmd_CHATFACE(game, thing_id):
170 game.tui.draw_face = thing_id
171 game.tui.do_refresh = True
172 cmd_CHATFACE.argtypes = 'int:pos'
174 def cmd_PLAYER_ID(game, player_id):
175 game.player_id = player_id
176 cmd_PLAYER_ID.argtypes = 'int:nonneg'
178 def cmd_PLAYERS_HAT_CHARS(game, hat_chars):
179 game.players_hat_chars_new = hat_chars
180 cmd_PLAYERS_HAT_CHARS.argtypes = 'string'
182 def cmd_THING(game, yx, thing_type, protection, thing_id, portable, commandable):
183 t = game.get_thing_temp(thing_id)
185 t = ThingBase(game, thing_id)
186 game.things_new += [t]
189 t.protection = protection
190 t.portable = portable
191 t.commandable = commandable
192 cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type char int:nonneg bool bool'
194 def cmd_THING_NAME(game, thing_id, name):
195 t = game.get_thing_temp(thing_id)
197 cmd_THING_NAME.argtypes = 'int:pos string'
199 def cmd_THING_FACE(game, thing_id, face):
200 t = game.get_thing_temp(thing_id)
202 cmd_THING_FACE.argtypes = 'int:pos string'
204 def cmd_THING_HAT(game, thing_id, hat):
205 t = game.get_thing_temp(thing_id)
207 cmd_THING_HAT.argtypes = 'int:pos string'
209 def cmd_THING_DESIGN(game, thing_id, size, design):
210 t = game.get_thing_temp(thing_id)
211 t.design = [size, design]
212 cmd_THING_DESIGN.argtypes = 'int:pos yx_tuple string'
214 def cmd_THING_CHAR(game, thing_id, c):
215 t = game.get_thing_temp(thing_id)
217 cmd_THING_CHAR.argtypes = 'int:pos char'
219 def cmd_MAP(game, geometry, size, content):
220 map_geometry_class = globals()['MapGeometry' + geometry]
221 game.map_geometry_new = map_geometry_class(size)
222 game.map_content_new = content
223 if type(game.map_geometry_new) == MapGeometrySquare:
224 game.tui.movement_keys = {
225 game.tui.keys['square_move_up']: 'UP',
226 game.tui.keys['square_move_left']: 'LEFT',
227 game.tui.keys['square_move_down']: 'DOWN',
228 game.tui.keys['square_move_right']: 'RIGHT',
230 elif type(game.map_geometry_new) == MapGeometryHex:
231 game.tui.movement_keys = {
232 game.tui.keys['hex_move_upleft']: 'UPLEFT',
233 game.tui.keys['hex_move_upright']: 'UPRIGHT',
234 game.tui.keys['hex_move_right']: 'RIGHT',
235 game.tui.keys['hex_move_downright']: 'DOWNRIGHT',
236 game.tui.keys['hex_move_downleft']: 'DOWNLEFT',
237 game.tui.keys['hex_move_left']: 'LEFT',
239 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
241 def cmd_FOV(game, content):
242 game.fov_new = content
243 cmd_FOV.argtypes = 'string'
245 def cmd_MAP_CONTROL(game, content):
246 game.map_control_content_new = content
247 cmd_MAP_CONTROL.argtypes = 'string'
249 def cmd_GAME_STATE_COMPLETE(game):
250 game.tui.do_refresh = True
251 game.tui.info_cached = None
252 game.things = game.things_new
253 game.portals = game.portals_new
254 game.annotations = game.annotations_new
255 game.fov = game.fov_new
256 game.map_geometry = game.map_geometry_new
257 game.map_content = game.map_content_new
258 game.map_control_content = game.map_control_content_new
259 game.player = game.get_thing(game.player_id)
260 game.players_hat_chars = game.players_hat_chars_new
261 game.bladder_pressure = game.bladder_pressure_new
262 game.energy = game.energy_new
263 game.turn_complete = True
264 if game.tui.mode.name == 'post_login_wait':
265 game.tui.switch_mode('play')
266 cmd_GAME_STATE_COMPLETE.argtypes = ''
268 def cmd_PORTAL(game, position, msg):
269 game.portals_new[position] = msg
270 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
272 def cmd_PLAY_ERROR(game, msg):
273 game.tui.log_msg('? ' + msg)
274 game.tui.flash = True
275 game.tui.do_refresh = True
276 cmd_PLAY_ERROR.argtypes = 'string'
278 def cmd_GAME_ERROR(game, msg):
279 game.tui.log_msg('? game error: ' + msg)
280 game.tui.do_refresh = True
281 cmd_GAME_ERROR.argtypes = 'string'
283 def cmd_ARGUMENT_ERROR(game, msg):
284 game.tui.log_msg('? syntax error: ' + msg)
285 game.tui.do_refresh = True
286 cmd_ARGUMENT_ERROR.argtypes = 'string'
288 def cmd_ANNOTATION(game, position, msg):
289 game.annotations_new[position] = msg
290 if game.tui.mode.shows_info:
291 game.tui.do_refresh = True
292 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
294 def cmd_TASKS(game, tasks_comma_separated):
295 game.tasks = tasks_comma_separated.split(',')
296 game.tui.mode_write.legal = 'WRITE' in game.tasks
297 game.tui.mode_command_thing.legal = 'COMMAND' in game.tasks
298 game.tui.mode_take_thing.legal = 'PICK_UP' in game.tasks
299 game.tui.mode_drop_thing.legal = 'DROP' in game.tasks
300 cmd_TASKS.argtypes = 'string'
302 def cmd_THING_TYPE(game, thing_type, symbol_hint):
303 game.thing_types[thing_type] = symbol_hint
304 cmd_THING_TYPE.argtypes = 'string char'
306 def cmd_THING_INSTALLED(game, thing_id):
307 game.get_thing_temp(thing_id).installed = True
308 cmd_THING_INSTALLED.argtypes = 'int:pos'
310 def cmd_THING_CARRYING(game, thing_id, carried_id):
311 game.get_thing_temp(thing_id).carrying = game.get_thing_temp(carried_id)
312 cmd_THING_CARRYING.argtypes = 'int:pos int:pos'
314 def cmd_TERRAIN(game, terrain_char, terrain_desc):
315 game.terrains[terrain_char] = terrain_desc
316 cmd_TERRAIN.argtypes = 'char string'
320 cmd_PONG.argtypes = ''
322 def cmd_DEFAULT_COLORS(game):
323 game.tui.set_default_colors()
324 cmd_DEFAULT_COLORS.argtypes = ''
326 def cmd_RANDOM_COLORS(game):
327 game.tui.set_random_colors()
328 cmd_RANDOM_COLORS.argtypes = ''
330 def cmd_STATS(game, bladder_pressure, energy):
331 game.bladder_pressure_new = bladder_pressure
332 game.energy_new = energy
333 cmd_STATS.argtypes = 'int:nonneg int'
335 class Game(GameBase):
336 turn_complete = False
341 def __init__(self, *args, **kwargs):
342 super().__init__(*args, **kwargs)
343 self.register_command(cmd_LOGIN_OK)
344 self.register_command(cmd_ADMIN_OK)
345 self.register_command(cmd_PONG)
346 self.register_command(cmd_CHAT)
347 self.register_command(cmd_CHATFACE)
348 self.register_command(cmd_REPLY)
349 self.register_command(cmd_PLAYER_ID)
350 self.register_command(cmd_TURN)
351 self.register_command(cmd_OTHER_WIPE)
352 self.register_command(cmd_THING)
353 self.register_command(cmd_THING_TYPE)
354 self.register_command(cmd_THING_NAME)
355 self.register_command(cmd_THING_CHAR)
356 self.register_command(cmd_THING_FACE)
357 self.register_command(cmd_THING_HAT)
358 self.register_command(cmd_THING_DESIGN)
359 self.register_command(cmd_THING_CARRYING)
360 self.register_command(cmd_THING_INSTALLED)
361 self.register_command(cmd_TERRAIN)
362 self.register_command(cmd_MAP)
363 self.register_command(cmd_MAP_CONTROL)
364 self.register_command(cmd_PORTAL)
365 self.register_command(cmd_ANNOTATION)
366 self.register_command(cmd_GAME_STATE_COMPLETE)
367 self.register_command(cmd_PLAYERS_HAT_CHARS)
368 self.register_command(cmd_ARGUMENT_ERROR)
369 self.register_command(cmd_GAME_ERROR)
370 self.register_command(cmd_PLAY_ERROR)
371 self.register_command(cmd_TASKS)
372 self.register_command(cmd_FOV)
373 self.register_command(cmd_DEFAULT_COLORS)
374 self.register_command(cmd_RANDOM_COLORS)
375 self.register_command(cmd_STATS)
376 self.map_content = ''
377 self.players_hat_chars = ''
379 self.annotations = {}
380 self.annotations_new = {}
382 self.portals_new = {}
386 def get_string_options(self, string_option_type):
387 if string_option_type == 'map_geometry':
388 return ['Hex', 'Square']
389 elif string_option_type == 'thing_type':
390 return self.thing_types.keys()
393 def get_command(self, command_name):
394 from functools import partial
395 f = partial(self.commands[command_name], self)
396 f.argtypes = self.commands[command_name].argtypes
399 def get_thing_temp(self, id_):
400 for thing in self.things_new:
407 def __init__(self, name, has_input_prompt=False, shows_info=False,
408 is_intro=False, is_single_char_entry=False):
410 self.short_desc = mode_helps[name]['short']
411 self.available_modes = []
412 self.available_actions = []
413 self.has_input_prompt = has_input_prompt
414 self.shows_info = shows_info
415 self.is_intro = is_intro
416 self.help_intro = mode_helps[name]['long']
417 self.intro_msg = mode_helps[name]['intro']
418 self.is_single_char_entry = is_single_char_entry
421 def iter_available_modes(self, tui):
422 for mode_name in self.available_modes:
423 mode = getattr(tui, 'mode_' + mode_name)
426 key = tui.keys['switch_to_' + mode.name]
429 def list_available_modes(self, tui):
431 if len(self.available_modes) > 0:
432 msg = 'Other modes available from here:\n'
433 for mode, key in self.iter_available_modes(tui):
434 msg += '[%s] – %s\n' % (key, mode.short_desc)
437 def mode_switch_on_key(self, tui, key_pressed):
438 for mode, key in self.iter_available_modes(tui):
439 if key_pressed == key:
440 tui.switch_mode(mode.name)
445 mode_admin_enter = Mode('admin_enter', has_input_prompt=True)
446 mode_admin = Mode('admin')
447 mode_play = Mode('play')
448 mode_study = Mode('study', shows_info=True)
449 mode_write = Mode('write', is_single_char_entry=True)
450 mode_edit = Mode('edit')
451 mode_control_pw_type = Mode('control_pw_type', has_input_prompt=True)
452 mode_control_pw_pw = Mode('control_pw_pw', has_input_prompt=True)
453 mode_control_tile_type = Mode('control_tile_type', has_input_prompt=True)
454 mode_control_tile_draw = Mode('control_tile_draw')
455 mode_admin_thing_protect = Mode('admin_thing_protect', has_input_prompt=True)
456 mode_annotate = Mode('annotate', has_input_prompt=True, shows_info=True)
457 mode_portal = Mode('portal', has_input_prompt=True, shows_info=True)
458 mode_chat = Mode('chat', has_input_prompt=True)
459 mode_waiting_for_server = Mode('waiting_for_server', is_intro=True)
460 mode_login = Mode('login', has_input_prompt=True, is_intro=True)
461 mode_post_login_wait = Mode('post_login_wait', is_intro=True)
462 mode_password = Mode('password', has_input_prompt=True)
463 mode_name_thing = Mode('name_thing', has_input_prompt=True, shows_info=True)
464 mode_command_thing = Mode('command_thing', has_input_prompt=True)
465 mode_take_thing = Mode('take_thing', has_input_prompt=True)
466 mode_drop_thing = Mode('drop_thing', has_input_prompt=True)
467 mode_enter_face = Mode('enter_face', has_input_prompt=True)
468 mode_enter_design = Mode('enter_design', has_input_prompt=True)
472 def __init__(self, host):
475 self.mode_play.available_modes = ["chat", "study", "edit", "admin_enter",
476 "command_thing", "take_thing",
478 self.mode_play.available_actions = ["move", "teleport", "door", "consume",
479 "install", "wear", "spin", "dance"]
480 self.mode_study.available_modes = ["chat", "play", "admin_enter", "edit"]
481 self.mode_study.available_actions = ["toggle_map_mode", "move_explorer"]
482 self.mode_admin.available_modes = ["admin_thing_protect", "control_pw_type",
483 "control_tile_type", "chat",
484 "study", "play", "edit"]
485 self.mode_admin.available_actions = ["move", "toggle_map_mode"]
486 self.mode_control_tile_draw.available_modes = ["admin_enter"]
487 self.mode_control_tile_draw.available_actions = ["move_explorer",
489 self.mode_edit.available_modes = ["write", "annotate", "portal",
490 "name_thing", "enter_face", "enter_design",
492 "chat", "study", "play", "admin_enter"]
493 self.mode_edit.available_actions = ["move", "flatten", "install",
496 self.socket = ClientSocket(host, self.socket_log)
499 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'
505 self.switch_mode('waiting_for_server')
507 'switch_to_chat': 't',
508 'switch_to_play': 'p',
509 'switch_to_password': 'P',
510 'switch_to_annotate': 'M',
511 'switch_to_portal': 'T',
512 'switch_to_study': '?',
513 'switch_to_edit': 'E',
514 'switch_to_write': 'm',
515 'switch_to_name_thing': 'N',
516 'switch_to_command_thing': 'O',
517 'switch_to_admin_enter': 'A',
518 'switch_to_control_pw_type': 'C',
519 'switch_to_control_tile_type': 'Q',
520 'switch_to_admin_thing_protect': 'T',
522 'switch_to_enter_face': 'f',
523 'switch_to_enter_design': 'D',
524 'switch_to_take_thing': 'z',
525 'switch_to_drop_thing': 'u',
534 'toggle_map_mode': 'L',
535 'toggle_tile_draw': 'm',
536 'hex_move_upleft': 'w',
537 'hex_move_upright': 'e',
538 'hex_move_right': 'd',
539 'hex_move_downright': 'x',
540 'hex_move_downleft': 'y',
541 'hex_move_left': 'a',
542 'square_move_up': 'w',
543 'square_move_left': 'a',
544 'square_move_down': 's',
545 'square_move_right': 'd',
547 if os.path.isfile('config.json'):
548 with open('config.json', 'r') as f:
549 keys_conf = json.loads(f.read())
551 self.keys[k] = keys_conf[k]
552 self.show_help = False
553 self.input_lines = []
557 self.ascii_draw_stage = 0
558 self.full_ascii_draw = ''
559 self.offset = YX(0,0)
560 curses.wrapper(self.loop)
562 def update_on_connect(self):
563 self.socket.send('TASKS')
564 self.socket.send('TERRAINS')
565 self.socket.send('THING_TYPES')
566 self.switch_mode('login')
570 self.log_msg('@ attempting reconnect')
571 self.socket.send('QUIT')
572 # necessitated by some strange SSL race conditions with ws4py
573 time.sleep(0.1) # FIXME find out why exactly necessary
574 self.switch_mode('waiting_for_server')
575 self.socket.connect()
576 self.update_on_connect()
579 self.socket.send(msg)
580 if self.socket.disconnected:
581 self.do_refresh = True
583 def socket_log(self, msg):
584 self.log_msg('@ ' + msg)
586 def log_msg(self, msg):
588 if len(self.log) > 100:
589 self.log = self.log[-100:]
591 def restore_input_values(self):
592 if self.mode.name == 'annotate' and self.explorer in self.game.annotations:
593 self.input_ = self.game.annotations[self.explorer]
594 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
595 self.input_ = self.game.portals[self.explorer]
596 elif self.mode.name == 'password':
597 self.input_ = self.password
598 elif self.mode.name == 'name_thing':
599 if hasattr(self.game.player.carrying, 'name'):
600 self.input_ = self.game.player.carrying.name
601 elif self.mode.name == 'admin_thing_protect':
602 if hasattr(self.game.player.carrying, 'protection'):
603 self.input_ = self.game.player.carrying.protection
604 elif self.mode.name == 'enter_face':
605 start = self.ascii_draw_stage * 6
606 end = (self.ascii_draw_stage + 1) * 6
607 self.input_ = self.game.player.face[start:end]
608 elif self.mode.name == 'enter_design':
609 width = self.game.player.carrying.design[0].x
610 start = self.ascii_draw_stage * width
611 end = (self.ascii_draw_stage + 1) * width
612 self.input_ = self.game.player.carrying.design[1][start:end]
614 def send_tile_control_command(self):
615 self.send('SET_TILE_CONTROL %s %s' %
616 (self.explorer, quote(self.tile_control_char)))
618 def toggle_map_mode(self):
619 if self.map_mode == 'terrain only':
620 self.map_mode = 'terrain + annotations'
621 elif self.map_mode == 'terrain + annotations':
622 self.map_mode = 'terrain + things'
623 elif self.map_mode == 'terrain + things':
624 self.map_mode = 'protections'
625 elif self.map_mode == 'protections':
626 self.map_mode = 'terrain only'
628 def switch_mode(self, mode_name):
630 def fail(msg, return_mode='play'):
631 self.log_msg('? ' + msg)
633 self.switch_mode(return_mode)
635 if self.mode and self.mode.name == 'control_tile_draw':
636 self.log_msg('@ finished tile protection drawing.')
637 self.draw_face = False
638 self.tile_draw = False
639 self.ascii_draw_stage = 0
640 self.full_ascii_draw = ''
641 if mode_name == 'command_thing' and\
642 (not self.game.player.carrying or
643 not self.game.player.carrying.commandable):
644 return fail('not carrying anything commandable')
645 if mode_name == 'name_thing' and not self.game.player.carrying:
646 return fail('not carrying anything to re-name', 'edit')
647 if mode_name == 'admin_thing_protect' and not self.game.player.carrying:
648 return fail('not carrying anything to protect')
649 if mode_name == 'take_thing' and self.game.player.carrying:
650 return fail('already carrying something')
651 if mode_name == 'drop_thing' and not self.game.player.carrying:
652 return fail('not carrying anything droppable')
653 if mode_name == 'enter_design' and\
654 (not self.game.player.carrying or
655 not hasattr(self.game.player.carrying, 'design')):
656 return fail('not carrying designable to edit', 'edit')
657 if mode_name == 'admin_enter' and self.is_admin:
659 self.mode = getattr(self, 'mode_' + mode_name)
660 if self.mode.name in {'control_tile_draw', 'control_tile_type',
662 self.map_mode = 'protections'
663 elif self.mode.name != 'edit':
664 self.map_mode = 'terrain + things'
665 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
666 self.explorer = YX(self.game.player.position.y,
667 self.game.player.position.x)
668 if self.mode.is_single_char_entry:
669 self.show_help = True
670 if len(self.mode.intro_msg) > 0:
671 self.log_msg(self.mode.intro_msg)
672 if self.mode.name == 'login':
674 self.send('LOGIN ' + quote(self.login_name))
676 self.log_msg('@ enter username')
677 elif self.mode.name == 'take_thing':
678 self.log_msg('Portable things in reach for pick-up:')
680 'HERE': YX(0, 0), 'LEFT': YX(0, -1), 'RIGHT': YX(0, 1)
682 if type(self.game.map_geometry) == MapGeometrySquare:
683 directed_moves['UP'] = YX(-1, 0)
684 directed_moves['DOWN'] = YX(1, 0)
685 elif type(self.game.map_geometry) == MapGeometryHex:
686 if self.game.player.position.y % 2:
687 directed_moves['UPLEFT'] = YX(-1, 0)
688 directed_moves['UPRIGHT'] = YX(-1, 1)
689 directed_moves['DOWNLEFT'] = YX(1, 0)
690 directed_moves['DOWNRIGHT'] = YX(1, 1)
692 directed_moves['UPLEFT'] = YX(-1, -1)
693 directed_moves['UPRIGHT'] = YX(-1, 0)
694 directed_moves['DOWNLEFT'] = YX(1, -1)
695 directed_moves['DOWNRIGHT'] = YX(1, 0)
697 for direction in directed_moves:
698 move = directed_moves[direction]
699 select_range[direction] = self.game.player.position + move
700 self.selectables = []
702 for direction in select_range:
703 for t in [t for t in self.game.things
704 if t.portable and t.position == select_range[direction]]:
705 self.selectables += [t.id_]
706 directions += [direction]
707 if len(self.selectables) == 0:
708 return fail('nothing to pick-up')
710 for i in range(len(self.selectables)):
711 t = self.game.get_thing(self.selectables[i])
712 self.log_msg('%s %s: %s' % (i, directions[i],
713 self.get_thing_info(t)))
714 elif self.mode.name == 'drop_thing':
715 self.log_msg('Direction to drop thing to:')
717 ['HERE'] + list(self.game.tui.movement_keys.values())
718 for i in range(len(self.selectables)):
719 self.log_msg(str(i) + ': ' + self.selectables[i])
720 elif self.mode.name == 'enter_design':
721 if self.game.player.carrying.type_ == 'Hat':
722 self.log_msg('@ The design you enter must be %s lines of max %s '
723 'characters width each'
724 % (self.game.player.carrying.design[0].y,
725 self.game.player.carrying.design[0].x))
726 self.log_msg('@ Legal characters: ' + self.game.players_hat_chars)
727 self.log_msg('@ (Eat cookies to extend the ASCII characters available for drawing.)')
729 self.log_msg('@ Width of first line determines maximum width for remaining design')
730 self.log_msg('@ Finish design by entering an empty line (multiple space characters do not count as empty)')
731 elif self.mode.name == 'command_thing':
732 self.send('TASK:COMMAND ' + quote('HELP'))
733 elif self.mode.name == 'control_pw_pw':
734 self.log_msg('@ enter protection password for "%s":' % self.tile_control_char)
735 elif self.mode.name == 'control_tile_draw':
736 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']))
738 self.restore_input_values()
740 def set_default_colors(self):
741 if curses.can_change_color():
742 curses.init_color(7, 1000, 1000, 1000)
743 curses.init_color(0, 0, 0, 0)
744 self.do_refresh = True
746 def set_random_colors(self):
750 return int(offset + random.random()*375)
752 if curses.can_change_color():
753 curses.init_color(7, rand(625), rand(625), rand(625))
754 curses.init_color(0, rand(0), rand(0), rand(0))
755 self.do_refresh = True
759 return self.info_cached
760 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
762 if len(self.game.fov) > pos_i and self.game.fov[pos_i] != '.':
763 info_to_cache += 'outside field of view'
765 for t in self.game.things:
766 if t.position == self.explorer:
767 info_to_cache += '%s' % self.get_thing_info(t, True)
768 terrain_char = self.game.map_content[pos_i]
770 if terrain_char in self.game.terrains:
771 terrain_desc = self.game.terrains[terrain_char]
772 info_to_cache += 'TERRAIN: %s (%s' % (terrain_char,
774 protection = self.game.map_control_content[pos_i]
775 if protection != '.':
776 info_to_cache += '/protection:%s' % protection
777 info_to_cache += ')\n'
778 if self.explorer in self.game.portals:
779 info_to_cache += 'PORTAL: ' +\
780 self.game.portals[self.explorer] + '\n'
781 if self.explorer in self.game.annotations:
782 info_to_cache += 'ANNOTATION: ' +\
783 self.game.annotations[self.explorer]
784 self.info_cached = info_to_cache
785 return self.info_cached
787 def get_thing_info(self, t, detailed=False):
791 info += self.game.thing_types[t.type_]
792 if hasattr(t, 'thing_char'):
794 if hasattr(t, 'name'):
795 info += ': %s' % t.name
796 info += ' (%s' % t.type_
797 if hasattr(t, 'installed'):
799 if t.type_ == 'Bottle':
800 if t.thing_char == '_':
802 elif t.thing_char == '~':
805 protection = t.protection
806 if protection != '.':
807 info += '/protection:%s' % protection
809 if hasattr(t, 'hat') or hasattr(t, 'face'):
810 info += '----------\n'
811 if hasattr(t, 'hat'):
812 info += '| %s |\n' % t.hat[0:6]
813 info += '| %s |\n' % t.hat[6:12]
814 info += '| %s |\n' % t.hat[12:18]
815 if hasattr(t, 'face'):
816 info += '| %s |\n' % t.face[0:6]
817 info += '| %s |\n' % t.face[6:12]
818 info += '| %s |\n' % t.face[12:18]
819 info += '----------\n'
820 if hasattr(t, 'design'):
821 line_length = t.design[0].x
823 for i in range(t.design[0].y):
824 start = i * line_length
825 end = (i + 1) * line_length
826 lines += [t.design[1][start:end]]
827 info += '-' * (line_length + 4) + '\n'
829 info += '| %s |\n' % line
830 info += '-' * (line_length + 4) + '\n'
835 def loop(self, stdscr):
837 def safe_addstr(y, x, line):
838 if y < self.size.y - 1 or x + len(line) < self.size.x:
839 stdscr.addstr(y, x, line, curses.color_pair(1))
840 else: # workaround to <https://stackoverflow.com/q/7063128>
841 cut_i = self.size.x - x - 1
843 last_char = line[cut_i]
844 stdscr.addstr(y, self.size.x - 2, last_char, curses.color_pair(1))
845 stdscr.insstr(y, self.size.x - 2, ' ')
846 stdscr.addstr(y, x, cut, curses.color_pair(1))
848 def handle_input(msg):
849 command, args = self.parser.parse(msg)
852 def task_action_on(action):
853 return action_tasks[action] in self.game.tasks
855 def msg_into_lines_of_width(msg, width):
859 for i in range(len(msg)):
860 if x >= width or msg[i] == "\n":
872 def reset_screen_size():
873 self.size = YX(*stdscr.getmaxyx())
874 self.size = self.size - YX(self.size.y % 4, 0)
875 self.size = self.size - YX(0, self.size.x % 4)
876 self.left_window_width = min(52, int(self.size.x / 2))
877 self.right_window_width = self.size.x - self.left_window_width
879 def recalc_input_lines():
880 if not self.mode.has_input_prompt:
881 self.input_lines = []
883 self.input_lines = msg_into_lines_of_width(input_prompt
885 self.right_window_width)
887 def move_explorer(direction):
888 target = self.game.map_geometry.move_yx(self.explorer, direction)
890 self.info_cached = None
891 self.explorer = target
893 self.send_tile_control_command()
899 for line in self.log:
900 lines += msg_into_lines_of_width(line, self.right_window_width)
903 max_y = self.size.y - len(self.input_lines)
904 for i in range(len(lines)):
905 if (i >= max_y - height_header):
907 safe_addstr(max_y - i - 1, self.left_window_width, lines[i])
910 info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info())
911 lines = msg_into_lines_of_width(info, self.right_window_width)
913 for i in range(len(lines)):
914 y = height_header + i
915 if y >= self.size.y - len(self.input_lines):
917 safe_addstr(y, self.left_window_width, lines[i])
920 y = self.size.y - len(self.input_lines)
921 for i in range(len(self.input_lines)):
922 safe_addstr(y, self.left_window_width, self.input_lines[i])
926 stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy,
927 self.game.bladder_pressure)
928 safe_addstr(0, self.left_window_width, stats)
931 help = "hit [%s] for help" % self.keys['help']
932 if self.mode.has_input_prompt:
933 help = "enter /help for help"
934 safe_addstr(1, self.left_window_width,
935 'MODE: %s – %s' % (self.mode.short_desc, help))
938 if (not self.game.turn_complete) and len(self.map_lines) == 0:
940 if self.game.turn_complete:
942 for y in range(self.game.map_geometry.size.y):
943 start = self.game.map_geometry.size.x * y
944 end = start + self.game.map_geometry.size.x
945 if self.map_mode == 'protections':
946 map_lines_split += [[c + ' ' for c
947 in self.game.map_control_content[start:end]]]
949 map_lines_split += [[c + ' ' for c
950 in self.game.map_content[start:end]]]
951 if self.map_mode == 'terrain + annotations':
952 for p in self.game.annotations:
953 map_lines_split[p.y][p.x] = 'A '
954 elif self.map_mode == 'terrain + things':
955 for p in self.game.portals.keys():
956 original = map_lines_split[p.y][p.x]
957 map_lines_split[p.y][p.x] = original[0] + 'P'
960 def draw_thing(t, used_positions):
961 symbol = self.game.thing_types[t.type_]
963 if hasattr(t, 'thing_char'):
964 meta_char = t.thing_char
965 if t.position in used_positions:
967 if hasattr(t, 'carrying') and t.carrying:
969 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
970 used_positions += [t.position]
972 for t in [t for t in self.game.things if t.type_ != 'Player']:
973 draw_thing(t, used_positions)
974 for t in [t for t in self.game.things if t.type_ == 'Player']:
975 draw_thing(t, used_positions)
976 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
977 map_lines_split[self.explorer.y][self.explorer.x] = '??'
978 elif self.map_mode != 'terrain + things':
979 map_lines_split[self.game.player.position.y]\
980 [self.game.player.position.x] = '??'
982 if type(self.game.map_geometry) == MapGeometryHex:
984 for line in map_lines_split:
985 self.map_lines += [indent * ' ' + ''.join(line)]
986 indent = 0 if indent else 1
988 for line in map_lines_split:
989 self.map_lines += [''.join(line)]
990 window_center = YX(int(self.size.y / 2),
991 int(self.left_window_width / 2))
992 center = self.game.player.position
993 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
994 center = self.explorer
995 center = YX(center.y, center.x * 2)
996 self.offset = center - window_center
997 if type(self.game.map_geometry) == MapGeometryHex and self.offset.y % 2:
998 self.offset += YX(0, 1)
999 term_y = max(0, -self.offset.y)
1000 term_x = max(0, -self.offset.x)
1001 map_y = max(0, self.offset.y)
1002 map_x = max(0, self.offset.x)
1003 while term_y < self.size.y and map_y < len(self.map_lines):
1004 to_draw = self.map_lines[map_y][map_x:self.left_window_width + self.offset.x]
1005 safe_addstr(term_y, term_x, to_draw)
1010 players = [t for t in self.game.things if t.type_ == 'Player']
1011 players.sort(key=lambda t: len(t.name))
1013 shrink_offset = max(0, (self.size.y - self.left_window_width // 2) // 2)
1016 offset_y = y - shrink_offset
1017 max_len = max(5, (self.left_window_width // 2) - (offset_y * 2) - 8)
1019 if len(name) > max_len:
1020 name = name[:max_len - 1] + '…'
1021 safe_addstr(y, 0, '@%s:%s' % (t.thing_char, name))
1023 if y >= self.size.y:
1026 def draw_face_popup():
1027 t = self.game.get_thing(self.draw_face)
1028 if not t or not hasattr(t, 'face'):
1029 self.draw_face = False
1032 start_x = self.left_window_width - 10
1033 def draw_body_part(body_part, end_y):
1034 safe_addstr(end_y - 3, start_x, '----------')
1035 safe_addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |')
1036 safe_addstr(end_y - 1, start_x, '| ' + body_part[6:12] + ' |')
1037 safe_addstr(end_y, start_x, '| ' + body_part[12:18] + ' |')
1039 if hasattr(t, 'face'):
1040 draw_body_part(t.face, self.size.y - 3)
1041 if hasattr(t, 'hat'):
1042 draw_body_part(t.hat, self.size.y - 6)
1043 safe_addstr(self.size.y - 2, start_x, '----------')
1046 name = name[:6 - 1] + '…'
1047 safe_addstr(self.size.y - 1, start_x,
1048 '@%s:%s' % (t.thing_char, name))
1051 content = "%s help\n\n%s\n\n" % (self.mode.short_desc,
1052 self.mode.help_intro)
1053 if len(self.mode.available_actions) > 0:
1054 content += "Available actions:\n"
1055 for action in self.mode.available_actions:
1056 if action in action_tasks:
1057 if action_tasks[action] not in self.game.tasks:
1059 if action == 'move_explorer':
1061 if action == 'move':
1062 key = ','.join(self.movement_keys)
1064 key = self.keys[action]
1065 content += '[%s] – %s\n' % (key, action_descriptions[action])
1067 content += self.mode.list_available_modes(self)
1068 for i in range(self.size.y):
1070 self.left_window_width * (not self.mode.has_input_prompt),
1071 ' ' * self.left_window_width)
1073 for line in content.split('\n'):
1074 lines += msg_into_lines_of_width(line, self.right_window_width)
1075 for i in range(len(lines)):
1076 if i >= self.size.y:
1079 self.left_window_width * (not self.mode.has_input_prompt),
1084 stdscr.bkgd(' ', curses.color_pair(1))
1085 recalc_input_lines()
1086 if self.mode.has_input_prompt:
1088 if self.mode.shows_info:
1093 if not self.mode.is_intro:
1098 if self.mode.name in {'chat', 'play'}:
1103 def pick_selectable(task_name):
1105 i = int(self.input_)
1106 if i < 0 or i >= len(self.selectables):
1107 self.log_msg('? invalid index, aborted')
1109 self.send('TASK:%s %s' % (task_name, self.selectables[i]))
1111 self.log_msg('? invalid index, aborted')
1113 self.switch_mode('play')
1115 def enter_ascii_art(command, height, width,
1116 with_pw=False, with_size=False):
1117 if with_size and self.ascii_draw_stage == 0:
1118 width = len(self.input_)
1120 self.log_msg('? input too long, must be max 36; try again')
1121 # TODO: move max width mechanism server-side
1123 old_size = self.game.player.carrying.design[0]
1124 if width != old_size.x:
1125 # TODO: save remaining design?
1126 self.game.player.carrying.design[1] = ''
1127 self.game.player.carrying.design[0] = YX(old_size.y, width)
1128 elif len(self.input_) > width:
1129 self.log_msg('? input too long, '
1130 'must be max %s; try again' % width)
1132 self.log_msg(' ' + self.input_)
1133 if with_size and self.input_ in {'', ' '}\
1134 and self.ascii_draw_stage > 0:
1135 height = self.ascii_draw_stage
1138 height = self.ascii_draw_stage + 2
1139 if len(self.input_) < width:
1140 self.input_ += ' ' * (width - len(self.input_))
1141 self.full_ascii_draw += self.input_
1143 old_size = self.game.player.carrying.design[0]
1144 self.game.player.carrying.design[0] = YX(height, old_size.x)
1145 self.ascii_draw_stage += 1
1146 if self.ascii_draw_stage < height:
1147 self.restore_input_values()
1149 if with_pw and with_size:
1150 self.send('%s_SIZE %s %s' % (command, YX(height, width),
1151 quote(self.password)))
1153 self.send('%s %s %s' % (command, quote(self.full_ascii_draw),
1154 quote(self.password)))
1156 self.send('%s %s' % (command, quote(self.full_ascii_draw)))
1157 self.full_ascii_draw = ""
1158 self.ascii_draw_stage = 0
1160 self.switch_mode('edit')
1162 action_descriptions = {
1164 'flatten': 'flatten surroundings',
1165 'teleport': 'teleport',
1166 'take_thing': 'pick up thing',
1167 'drop_thing': 'drop thing',
1168 'toggle_map_mode': 'toggle map view',
1169 'toggle_tile_draw': 'toggle protection character drawing',
1170 'install': '(un-)install',
1171 'wear': '(un-)wear',
1172 'door': 'open/close',
1173 'consume': 'consume',
1179 'flatten': 'FLATTEN_SURROUNDINGS',
1180 'take_thing': 'PICK_UP',
1181 'drop_thing': 'DROP',
1183 'install': 'INSTALL',
1186 'command': 'COMMAND',
1187 'consume': 'INTOXICATE',
1192 curses.curs_set(0) # hide cursor
1193 curses.start_color()
1194 self.set_default_colors()
1195 curses.init_pair(1, 7, 0)
1196 if not curses.can_change_color():
1197 self.log_msg('@ unfortunately, your terminal does not seem to '
1198 'support re-definition of colors; you might miss out '
1199 'on some color effects')
1202 self.explorer = YX(0, 0)
1204 store_widechar = False
1207 prev_disconnected = self.socket.disconnected
1208 self.socket.keep_connection_alive()
1209 if prev_disconnected and not self.socket.disconnected:
1210 self.update_on_connect()
1216 self.do_refresh = False
1217 for msg in self.socket.get_message():
1220 key = stdscr.getkey()
1221 self.do_refresh = True
1222 except curses.error:
1227 # workaround for <https://stackoverflow.com/a/56390915>
1229 store_widechar = False
1230 key = bytes([195, keycode]).decode()
1232 store_widechar = True
1234 self.show_help = False
1235 self.draw_face = False
1236 if key == 'KEY_RESIZE':
1238 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
1239 self.input_ = self.input_[:-1]
1240 elif (((not self.mode.is_intro) and keycode == 27) # Escape
1241 or (self.mode.has_input_prompt and key == '\n'
1242 and self.input_ == ''\
1243 and self.mode.name in {'chat', 'command_thing',
1244 'take_thing', 'drop_thing',
1246 if self.mode.name not in {'chat', 'play', 'study', 'edit'}:
1247 self.log_msg('@ aborted')
1248 self.switch_mode('play')
1249 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
1250 self.show_help = True
1252 self.restore_input_values()
1253 elif self.mode.has_input_prompt and key != '\n': # Return key
1255 max_length = self.right_window_width * self.size.y - len(input_prompt) - 1
1256 if len(self.input_) > max_length:
1257 self.input_ = self.input_[:max_length]
1258 elif key == self.keys['help'] and not self.mode.is_single_char_entry:
1259 self.show_help = True
1260 elif self.mode.name == 'login' and key == '\n':
1261 self.login_name = self.input_
1262 self.send('LOGIN ' + quote(self.input_))
1264 elif self.mode.name == 'enter_face' and key == '\n':
1265 enter_ascii_art('PLAYER_FACE', 3, 6)
1266 elif self.mode.name == 'enter_design' and key == '\n':
1267 if self.game.player.carrying.type_ == 'Hat':
1268 enter_ascii_art('THING_DESIGN',
1269 self.game.player.carrying.design[0].y,
1270 self.game.player.carrying.design[0].x, True)
1272 enter_ascii_art('THING_DESIGN',
1273 self.game.player.carrying.design[0].y,
1274 self.game.player.carrying.design[0].x,
1276 elif self.mode.name == 'take_thing' and key == '\n':
1277 pick_selectable('PICK_UP')
1278 elif self.mode.name == 'drop_thing' and key == '\n':
1279 pick_selectable('DROP')
1280 elif self.mode.name == 'command_thing' and key == '\n':
1281 self.send('TASK:COMMAND ' + quote(self.input_))
1283 elif self.mode.name == 'control_pw_pw' and key == '\n':
1284 if self.input_ == '':
1285 self.log_msg('@ aborted')
1287 self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
1288 self.log_msg('@ sent new password for protection character "%s"' % self.tile_control_char)
1289 self.switch_mode('admin')
1290 elif self.mode.name == 'password' and key == '\n':
1291 if self.input_ == '':
1293 self.password = self.input_
1294 self.switch_mode('edit')
1295 elif self.mode.name == 'admin_enter' and key == '\n':
1296 self.send('BECOME_ADMIN ' + quote(self.input_))
1297 self.switch_mode('play')
1298 elif self.mode.name == 'control_pw_type' and key == '\n':
1299 if len(self.input_) != 1:
1300 self.log_msg('@ entered non-single-char, therefore aborted')
1301 self.switch_mode('admin')
1303 self.tile_control_char = self.input_
1304 self.switch_mode('control_pw_pw')
1305 elif self.mode.name == 'admin_thing_protect' and key == '\n':
1306 if len(self.input_) != 1:
1307 self.log_msg('@ entered non-single-char, therefore aborted')
1309 self.send('THING_PROTECTION %s' % (quote(self.input_)))
1310 self.log_msg('@ sent new protection character for thing')
1311 self.switch_mode('admin')
1312 elif self.mode.name == 'control_tile_type' and key == '\n':
1313 if len(self.input_) != 1:
1314 self.log_msg('@ entered non-single-char, therefore aborted')
1315 self.switch_mode('admin')
1317 self.tile_control_char = self.input_
1318 self.switch_mode('control_tile_draw')
1319 elif self.mode.name == 'chat' and key == '\n':
1320 if self.input_ == '':
1322 if self.input_[0] == '/':
1323 if self.input_.startswith('/nick'):
1324 tokens = self.input_.split(maxsplit=1)
1325 if len(tokens) == 2:
1326 self.send('NICK ' + quote(tokens[1]))
1328 self.log_msg('? need login name')
1330 self.log_msg('? unknown command')
1332 self.send('ALL ' + quote(self.input_))
1334 elif self.mode.name == 'name_thing' and key == '\n':
1335 if self.input_ == '':
1337 self.send('THING_NAME %s %s' % (quote(self.input_),
1338 quote(self.password)))
1339 self.switch_mode('edit')
1340 elif self.mode.name == 'annotate' and key == '\n':
1341 if self.input_ == '':
1343 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
1344 quote(self.password)))
1345 self.switch_mode('edit')
1346 elif self.mode.name == 'portal' and key == '\n':
1347 if self.input_ == '':
1349 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
1350 quote(self.password)))
1351 self.switch_mode('edit')
1352 elif self.mode.name == 'study':
1353 if self.mode.mode_switch_on_key(self, key):
1355 elif key == self.keys['toggle_map_mode']:
1356 self.toggle_map_mode()
1357 elif key in self.movement_keys:
1358 move_explorer(self.movement_keys[key])
1359 elif self.mode.name == 'play':
1360 if self.mode.mode_switch_on_key(self, key):
1362 elif key == self.keys['door'] and task_action_on('door'):
1363 self.send('TASK:DOOR')
1364 elif key == self.keys['consume'] and task_action_on('consume'):
1365 self.send('TASK:INTOXICATE')
1366 elif key == self.keys['wear'] and task_action_on('wear'):
1367 self.send('TASK:WEAR')
1368 elif key == self.keys['spin'] and task_action_on('spin'):
1369 self.send('TASK:SPIN')
1370 elif key == self.keys['dance'] and task_action_on('dance'):
1371 self.send('TASK:DANCE')
1372 elif key == self.keys['teleport']:
1373 if self.game.player.position in self.game.portals:
1374 self.socket.host = self.game.portals[self.game.player.position]
1378 self.log_msg('? not standing on portal')
1379 elif key in self.movement_keys and task_action_on('move'):
1380 self.send('TASK:MOVE ' + self.movement_keys[key])
1381 elif self.mode.name == 'write':
1382 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
1383 self.switch_mode('edit')
1384 elif self.mode.name == 'control_tile_draw':
1385 if self.mode.mode_switch_on_key(self, key):
1387 elif key in self.movement_keys:
1388 move_explorer(self.movement_keys[key])
1389 elif key == self.keys['toggle_tile_draw']:
1390 self.tile_draw = False if self.tile_draw else True
1391 elif self.mode.name == 'admin':
1392 if self.mode.mode_switch_on_key(self, key):
1394 elif key == self.keys['toggle_map_mode']:
1395 self.toggle_map_mode()
1396 elif key in self.movement_keys and task_action_on('move'):
1397 self.send('TASK:MOVE ' + self.movement_keys[key])
1398 elif self.mode.name == 'edit':
1399 if self.mode.mode_switch_on_key(self, key):
1401 elif key == self.keys['flatten'] and task_action_on('flatten'):
1402 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
1403 elif key == self.keys['install'] and task_action_on('install'):
1404 self.send('TASK:INSTALL %s' % quote(self.password))
1405 elif key == self.keys['toggle_map_mode']:
1406 self.toggle_map_mode()
1407 elif key in self.movement_keys and task_action_on('move'):
1408 self.send('TASK:MOVE ' + self.movement_keys[key])
1410 if len(sys.argv) != 2:
1411 raise ArgError('wrong number of arguments, need game host')