7 from plomrogue.game import GameBase
8 from plomrogue.parser import Parser
9 from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex
10 from plomrogue.things import ThingBase
11 from plomrogue.misc import quote
12 from plomrogue.errors import BrokenSocketConnection, ArgError
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 from ws4py.client import WebSocketBaseClient
137 class WebSocketClient(WebSocketBaseClient):
139 def __init__(self, recv_handler, *args, **kwargs):
140 super().__init__(*args, **kwargs)
141 self.recv_handler = recv_handler
144 def received_message(self, message):
146 message = str(message)
147 self.recv_handler(message)
150 def plom_closed(self):
151 return self.client_terminated
153 from plomrogue.io_tcp import PlomSocket
154 class PlomSocketClient(PlomSocket):
156 def __init__(self, recv_handler, url):
158 self.recv_handler = recv_handler
159 host, port = url.split(':')
160 super().__init__(socket.create_connection((host, port)))
168 for msg in self.recv():
169 if msg == 'NEED_SSL':
170 self.socket = ssl.wrap_socket(self.socket)
172 self.recv_handler(msg)
173 except BrokenSocketConnection:
174 pass # we assume socket will be known as dead by now
176 def cmd_TURN(game, n):
177 game.turn_complete = False
178 cmd_TURN.argtypes = 'int:nonneg'
180 def cmd_OTHER_WIPE(game):
181 game.portals_new = {}
182 game.annotations_new = {}
184 cmd_OTHER_WIPE.argtypes = ''
186 def cmd_LOGIN_OK(game):
187 game.tui.switch_mode('post_login_wait')
188 game.tui.send('GET_GAMESTATE')
189 game.tui.log_msg('@ welcome!')
190 game.tui.log_msg('@ hint: see top of terminal for how to get help.')
191 game.tui.log_msg('@ hint: enter study mode to understand your environment.')
192 cmd_LOGIN_OK.argtypes = ''
194 def cmd_ADMIN_OK(game):
195 game.tui.is_admin = True
196 game.tui.log_msg('@ you now have admin rights')
197 game.tui.switch_mode('admin')
198 game.tui.do_refresh = True
199 cmd_ADMIN_OK.argtypes = ''
201 def cmd_REPLY(game, msg):
202 game.tui.log_msg('#MUSICPLAYER: ' + msg)
203 game.tui.do_refresh = True
204 cmd_REPLY.argtypes = 'string'
206 def cmd_CHAT(game, msg):
207 game.tui.log_msg('# ' + msg)
208 game.tui.do_refresh = True
209 cmd_CHAT.argtypes = 'string'
211 def cmd_CHATFACE(game, thing_id):
212 game.tui.draw_face = thing_id
213 game.tui.do_refresh = True
214 cmd_CHATFACE.argtypes = 'int:pos'
216 def cmd_PLAYER_ID(game, player_id):
217 game.player_id = player_id
218 cmd_PLAYER_ID.argtypes = 'int:nonneg'
220 def cmd_PLAYERS_HAT_CHARS(game, hat_chars):
221 game.players_hat_chars_new = hat_chars
222 cmd_PLAYERS_HAT_CHARS.argtypes = 'string'
224 def cmd_THING(game, yx, thing_type, protection, thing_id, portable, commandable):
225 t = game.get_thing_temp(thing_id)
227 t = ThingBase(game, thing_id)
228 game.things_new += [t]
231 t.protection = protection
232 t.portable = portable
233 t.commandable = commandable
234 cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type char int:nonneg bool bool'
236 def cmd_THING_NAME(game, thing_id, name):
237 t = game.get_thing_temp(thing_id)
239 cmd_THING_NAME.argtypes = 'int:pos string'
241 def cmd_THING_FACE(game, thing_id, face):
242 t = game.get_thing_temp(thing_id)
244 cmd_THING_FACE.argtypes = 'int:pos string'
246 def cmd_THING_HAT(game, thing_id, hat):
247 t = game.get_thing_temp(thing_id)
249 cmd_THING_HAT.argtypes = 'int:pos string'
251 def cmd_THING_DESIGN(game, thing_id, size, design):
252 t = game.get_thing_temp(thing_id)
253 t.design = [size, design]
254 cmd_THING_DESIGN.argtypes = 'int:pos yx_tuple string'
256 def cmd_THING_CHAR(game, thing_id, c):
257 t = game.get_thing_temp(thing_id)
259 cmd_THING_CHAR.argtypes = 'int:pos char'
261 def cmd_MAP(game, geometry, size, content):
262 map_geometry_class = globals()['MapGeometry' + geometry]
263 game.map_geometry_new = map_geometry_class(size)
264 game.map_content_new = content
265 if type(game.map_geometry_new) == MapGeometrySquare:
266 game.tui.movement_keys = {
267 game.tui.keys['square_move_up']: 'UP',
268 game.tui.keys['square_move_left']: 'LEFT',
269 game.tui.keys['square_move_down']: 'DOWN',
270 game.tui.keys['square_move_right']: 'RIGHT',
272 elif type(game.map_geometry_new) == MapGeometryHex:
273 game.tui.movement_keys = {
274 game.tui.keys['hex_move_upleft']: 'UPLEFT',
275 game.tui.keys['hex_move_upright']: 'UPRIGHT',
276 game.tui.keys['hex_move_right']: 'RIGHT',
277 game.tui.keys['hex_move_downright']: 'DOWNRIGHT',
278 game.tui.keys['hex_move_downleft']: 'DOWNLEFT',
279 game.tui.keys['hex_move_left']: 'LEFT',
281 cmd_MAP.argtypes = 'string:map_geometry yx_tuple:pos string'
283 def cmd_FOV(game, content):
284 game.fov_new = content
285 cmd_FOV.argtypes = 'string'
287 def cmd_MAP_CONTROL(game, content):
288 game.map_control_content_new = content
289 cmd_MAP_CONTROL.argtypes = 'string'
291 def cmd_GAME_STATE_COMPLETE(game):
292 game.tui.do_refresh = True
293 game.tui.info_cached = None
294 game.things = game.things_new
295 game.portals = game.portals_new
296 game.annotations = game.annotations_new
297 game.fov = game.fov_new
298 game.map_geometry = game.map_geometry_new
299 game.map_content = game.map_content_new
300 game.map_control_content = game.map_control_content_new
301 game.player = game.get_thing(game.player_id)
302 game.players_hat_chars = game.players_hat_chars_new
303 game.bladder_pressure = game.bladder_pressure_new
304 game.energy = game.energy_new
305 game.turn_complete = True
306 if game.tui.mode.name == 'post_login_wait':
307 game.tui.switch_mode('play')
308 cmd_GAME_STATE_COMPLETE.argtypes = ''
310 def cmd_PORTAL(game, position, msg):
311 game.portals_new[position] = msg
312 cmd_PORTAL.argtypes = 'yx_tuple:nonneg string'
314 def cmd_PLAY_ERROR(game, msg):
315 game.tui.log_msg('? ' + msg)
316 game.tui.flash = True
317 game.tui.do_refresh = True
318 cmd_PLAY_ERROR.argtypes = 'string'
320 def cmd_GAME_ERROR(game, msg):
321 game.tui.log_msg('? game error: ' + msg)
322 game.tui.do_refresh = True
323 cmd_GAME_ERROR.argtypes = 'string'
325 def cmd_ARGUMENT_ERROR(game, msg):
326 game.tui.log_msg('? syntax error: ' + msg)
327 game.tui.do_refresh = True
328 cmd_ARGUMENT_ERROR.argtypes = 'string'
330 def cmd_ANNOTATION(game, position, msg):
331 game.annotations_new[position] = msg
332 if game.tui.mode.shows_info:
333 game.tui.do_refresh = True
334 cmd_ANNOTATION.argtypes = 'yx_tuple:nonneg string'
336 def cmd_TASKS(game, tasks_comma_separated):
337 game.tasks = tasks_comma_separated.split(',')
338 game.tui.mode_write.legal = 'WRITE' in game.tasks
339 game.tui.mode_command_thing.legal = 'COMMAND' in game.tasks
340 game.tui.mode_take_thing.legal = 'PICK_UP' in game.tasks
341 game.tui.mode_drop_thing.legal = 'DROP' in game.tasks
342 cmd_TASKS.argtypes = 'string'
344 def cmd_THING_TYPE(game, thing_type, symbol_hint):
345 game.thing_types[thing_type] = symbol_hint
346 cmd_THING_TYPE.argtypes = 'string char'
348 def cmd_THING_INSTALLED(game, thing_id):
349 game.get_thing_temp(thing_id).installed = True
350 cmd_THING_INSTALLED.argtypes = 'int:pos'
352 def cmd_THING_CARRYING(game, thing_id, carried_id):
353 game.get_thing_temp(thing_id).carrying = game.get_thing_temp(carried_id)
354 cmd_THING_CARRYING.argtypes = 'int:pos int:pos'
356 def cmd_TERRAIN(game, terrain_char, terrain_desc):
357 game.terrains[terrain_char] = terrain_desc
358 cmd_TERRAIN.argtypes = 'char string'
362 cmd_PONG.argtypes = ''
364 def cmd_DEFAULT_COLORS(game):
365 game.tui.set_default_colors()
366 cmd_DEFAULT_COLORS.argtypes = ''
368 def cmd_RANDOM_COLORS(game):
369 game.tui.set_random_colors()
370 cmd_RANDOM_COLORS.argtypes = ''
372 def cmd_STATS(game, bladder_pressure, energy):
373 game.bladder_pressure_new = bladder_pressure
374 game.energy_new = energy
375 cmd_STATS.argtypes = 'int:nonneg int'
377 class Game(GameBase):
378 turn_complete = False
383 def __init__(self, *args, **kwargs):
384 super().__init__(*args, **kwargs)
385 self.register_command(cmd_LOGIN_OK)
386 self.register_command(cmd_ADMIN_OK)
387 self.register_command(cmd_PONG)
388 self.register_command(cmd_CHAT)
389 self.register_command(cmd_CHATFACE)
390 self.register_command(cmd_REPLY)
391 self.register_command(cmd_PLAYER_ID)
392 self.register_command(cmd_TURN)
393 self.register_command(cmd_OTHER_WIPE)
394 self.register_command(cmd_THING)
395 self.register_command(cmd_THING_TYPE)
396 self.register_command(cmd_THING_NAME)
397 self.register_command(cmd_THING_CHAR)
398 self.register_command(cmd_THING_FACE)
399 self.register_command(cmd_THING_HAT)
400 self.register_command(cmd_THING_DESIGN)
401 self.register_command(cmd_THING_CARRYING)
402 self.register_command(cmd_THING_INSTALLED)
403 self.register_command(cmd_TERRAIN)
404 self.register_command(cmd_MAP)
405 self.register_command(cmd_MAP_CONTROL)
406 self.register_command(cmd_PORTAL)
407 self.register_command(cmd_ANNOTATION)
408 self.register_command(cmd_GAME_STATE_COMPLETE)
409 self.register_command(cmd_PLAYERS_HAT_CHARS)
410 self.register_command(cmd_ARGUMENT_ERROR)
411 self.register_command(cmd_GAME_ERROR)
412 self.register_command(cmd_PLAY_ERROR)
413 self.register_command(cmd_TASKS)
414 self.register_command(cmd_FOV)
415 self.register_command(cmd_DEFAULT_COLORS)
416 self.register_command(cmd_RANDOM_COLORS)
417 self.register_command(cmd_STATS)
418 self.map_content = ''
419 self.players_hat_chars = ''
421 self.annotations = {}
422 self.annotations_new = {}
424 self.portals_new = {}
428 def get_string_options(self, string_option_type):
429 if string_option_type == 'map_geometry':
430 return ['Hex', 'Square']
431 elif string_option_type == 'thing_type':
432 return self.thing_types.keys()
435 def get_command(self, command_name):
436 from functools import partial
437 f = partial(self.commands[command_name], self)
438 f.argtypes = self.commands[command_name].argtypes
441 def get_thing_temp(self, id_):
442 for thing in self.things_new:
449 def __init__(self, name, has_input_prompt=False, shows_info=False,
450 is_intro=False, is_single_char_entry=False):
452 self.short_desc = mode_helps[name]['short']
453 self.available_modes = []
454 self.available_actions = []
455 self.has_input_prompt = has_input_prompt
456 self.shows_info = shows_info
457 self.is_intro = is_intro
458 self.help_intro = mode_helps[name]['long']
459 self.intro_msg = mode_helps[name]['intro']
460 self.is_single_char_entry = is_single_char_entry
463 def iter_available_modes(self, tui):
464 for mode_name in self.available_modes:
465 mode = getattr(tui, 'mode_' + mode_name)
468 key = tui.keys['switch_to_' + mode.name]
471 def list_available_modes(self, tui):
473 if len(self.available_modes) > 0:
474 msg = 'Other modes available from here:\n'
475 for mode, key in self.iter_available_modes(tui):
476 msg += '[%s] – %s\n' % (key, mode.short_desc)
479 def mode_switch_on_key(self, tui, key_pressed):
480 for mode, key in self.iter_available_modes(tui):
481 if key_pressed == key:
482 tui.switch_mode(mode.name)
487 mode_admin_enter = Mode('admin_enter', has_input_prompt=True)
488 mode_admin = Mode('admin')
489 mode_play = Mode('play')
490 mode_study = Mode('study', shows_info=True)
491 mode_write = Mode('write', is_single_char_entry=True)
492 mode_edit = Mode('edit')
493 mode_control_pw_type = Mode('control_pw_type', has_input_prompt=True)
494 mode_control_pw_pw = Mode('control_pw_pw', has_input_prompt=True)
495 mode_control_tile_type = Mode('control_tile_type', has_input_prompt=True)
496 mode_control_tile_draw = Mode('control_tile_draw')
497 mode_admin_thing_protect = Mode('admin_thing_protect', has_input_prompt=True)
498 mode_annotate = Mode('annotate', has_input_prompt=True, shows_info=True)
499 mode_portal = Mode('portal', has_input_prompt=True, shows_info=True)
500 mode_chat = Mode('chat', has_input_prompt=True)
501 mode_waiting_for_server = Mode('waiting_for_server', is_intro=True)
502 mode_login = Mode('login', has_input_prompt=True, is_intro=True)
503 mode_post_login_wait = Mode('post_login_wait', is_intro=True)
504 mode_password = Mode('password', has_input_prompt=True)
505 mode_name_thing = Mode('name_thing', has_input_prompt=True, shows_info=True)
506 mode_command_thing = Mode('command_thing', has_input_prompt=True)
507 mode_take_thing = Mode('take_thing', has_input_prompt=True)
508 mode_drop_thing = Mode('drop_thing', has_input_prompt=True)
509 mode_enter_face = Mode('enter_face', has_input_prompt=True)
510 mode_enter_design = Mode('enter_design', has_input_prompt=True)
514 def __init__(self, host):
517 self.mode_play.available_modes = ["chat", "study", "edit", "admin_enter",
518 "command_thing", "take_thing",
520 self.mode_play.available_actions = ["move", "teleport", "door", "consume",
521 "install", "wear", "spin", "dance"]
522 self.mode_study.available_modes = ["chat", "play", "admin_enter", "edit"]
523 self.mode_study.available_actions = ["toggle_map_mode", "move_explorer"]
524 self.mode_admin.available_modes = ["admin_thing_protect", "control_pw_type",
525 "control_tile_type", "chat",
526 "study", "play", "edit"]
527 self.mode_admin.available_actions = ["move", "toggle_map_mode"]
528 self.mode_control_tile_draw.available_modes = ["admin_enter"]
529 self.mode_control_tile_draw.available_actions = ["move_explorer",
531 self.mode_edit.available_modes = ["write", "annotate", "portal",
532 "name_thing", "enter_face", "enter_design",
534 "chat", "study", "play", "admin_enter"]
535 self.mode_edit.available_actions = ["move", "flatten", "install",
541 self.parser = Parser(self.game)
543 self.do_refresh = True
544 self.queue = queue.Queue()
545 self.login_name = None
546 self.map_mode = 'terrain + things'
547 self.password = 'foo'
548 self.switch_mode('waiting_for_server')
550 'switch_to_chat': 't',
551 'switch_to_play': 'p',
552 'switch_to_password': 'P',
553 'switch_to_annotate': 'M',
554 'switch_to_portal': 'T',
555 'switch_to_study': '?',
556 'switch_to_edit': 'E',
557 'switch_to_write': 'm',
558 'switch_to_name_thing': 'N',
559 'switch_to_command_thing': 'O',
560 'switch_to_admin_enter': 'A',
561 'switch_to_control_pw_type': 'C',
562 'switch_to_control_tile_type': 'Q',
563 'switch_to_admin_thing_protect': 'T',
565 'switch_to_enter_face': 'f',
566 'switch_to_enter_design': 'D',
567 'switch_to_take_thing': 'z',
568 'switch_to_drop_thing': 'u',
577 'toggle_map_mode': 'L',
578 'toggle_tile_draw': 'm',
579 'hex_move_upleft': 'w',
580 'hex_move_upright': 'e',
581 'hex_move_right': 'd',
582 'hex_move_downright': 'x',
583 'hex_move_downleft': 'y',
584 'hex_move_left': 'a',
585 'square_move_up': 'w',
586 'square_move_left': 'a',
587 'square_move_down': 's',
588 'square_move_right': 'd',
590 if os.path.isfile('config.json'):
591 with open('config.json', 'r') as f:
592 keys_conf = json.loads(f.read())
594 self.keys[k] = keys_conf[k]
595 self.show_help = False
596 self.disconnected = True
597 self.force_instant_connect = True
598 self.input_lines = []
602 self.ascii_draw_stage = 0
603 self.full_ascii_draw = ''
604 self.offset = YX(0,0)
605 curses.wrapper(self.loop)
609 def handle_recv(msg):
615 self.log_msg('@ attempting connect')
616 socket_client_class = PlomSocketClient
617 if self.host.startswith('ws://') or self.host.startswith('wss://'):
618 socket_client_class = WebSocketClient
620 self.socket = socket_client_class(handle_recv, self.host)
621 self.socket_thread = threading.Thread(target=self.socket.run)
622 self.socket_thread.start()
623 self.disconnected = False
624 self.game.thing_types = {}
625 self.game.terrains = {}
626 time.sleep(0.1) # give potential SSL negotation some time …
627 self.socket.send('TASKS')
628 self.socket.send('TERRAINS')
629 self.socket.send('THING_TYPES')
630 self.switch_mode('login')
631 except ConnectionRefusedError:
632 self.log_msg('@ server connect failure')
633 self.disconnected = True
634 self.switch_mode('waiting_for_server')
635 self.do_refresh = True
638 self.log_msg('@ attempting reconnect')
640 # necessitated by some strange SSL race conditions with ws4py
641 time.sleep(0.1) # FIXME find out why exactly necessary
642 self.switch_mode('waiting_for_server')
647 if hasattr(self.socket, 'plom_closed') and self.socket.plom_closed:
648 raise BrokenSocketConnection
649 self.socket.send(msg)
650 except (BrokenPipeError, BrokenSocketConnection):
651 self.log_msg('@ server disconnected :(')
652 self.disconnected = True
653 self.force_instant_connect = True
654 self.do_refresh = True
656 def log_msg(self, msg):
658 if len(self.log) > 100:
659 self.log = self.log[-100:]
661 def restore_input_values(self):
662 if self.mode.name == 'annotate' and self.explorer in self.game.annotations:
663 self.input_ = self.game.annotations[self.explorer]
664 elif self.mode.name == 'portal' and self.explorer in self.game.portals:
665 self.input_ = self.game.portals[self.explorer]
666 elif self.mode.name == 'password':
667 self.input_ = self.password
668 elif self.mode.name == 'name_thing':
669 if hasattr(self.game.player.carrying, 'name'):
670 self.input_ = self.game.player.carrying.name
671 elif self.mode.name == 'admin_thing_protect':
672 if hasattr(self.game.player.carrying, 'protection'):
673 self.input_ = self.game.player.carrying.protection
674 elif self.mode.name == 'enter_face':
675 start = self.ascii_draw_stage * 6
676 end = (self.ascii_draw_stage + 1) * 6
677 self.input_ = self.game.player.face[start:end]
678 elif self.mode.name == 'enter_design':
679 width = self.game.player.carrying.design[0].x
680 start = self.ascii_draw_stage * width
681 end = (self.ascii_draw_stage + 1) * width
682 self.input_ = self.game.player.carrying.design[1][start:end]
684 def send_tile_control_command(self):
685 self.send('SET_TILE_CONTROL %s %s' %
686 (self.explorer, quote(self.tile_control_char)))
688 def toggle_map_mode(self):
689 if self.map_mode == 'terrain only':
690 self.map_mode = 'terrain + annotations'
691 elif self.map_mode == 'terrain + annotations':
692 self.map_mode = 'terrain + things'
693 elif self.map_mode == 'terrain + things':
694 self.map_mode = 'protections'
695 elif self.map_mode == 'protections':
696 self.map_mode = 'terrain only'
698 def switch_mode(self, mode_name):
700 def fail(msg, return_mode='play'):
701 self.log_msg('? ' + msg)
703 self.switch_mode(return_mode)
705 if self.mode and self.mode.name == 'control_tile_draw':
706 self.log_msg('@ finished tile protection drawing.')
707 self.draw_face = False
708 self.tile_draw = False
709 if mode_name == 'command_thing' and\
710 (not self.game.player.carrying or
711 not self.game.player.carrying.commandable):
712 return fail('not carrying anything commandable')
713 if mode_name == 'name_thing' and not self.game.player.carrying:
714 return fail('not carrying anything to re-name', 'edit')
715 if mode_name == 'admin_thing_protect' and not self.game.player.carrying:
716 return fail('not carrying anything to protect')
717 if mode_name == 'take_thing' and self.game.player.carrying:
718 return fail('already carrying something')
719 if mode_name == 'drop_thing' and not self.game.player.carrying:
720 return fail('not carrying anything droppable')
721 if mode_name == 'enter_design' and\
722 (not self.game.player.carrying or
723 not hasattr(self.game.player.carrying, 'design')):
724 return fail('not carrying designable to edit', 'edit')
725 if mode_name == 'admin_enter' and self.is_admin:
727 self.mode = getattr(self, 'mode_' + mode_name)
728 if self.mode.name in {'control_tile_draw', 'control_tile_type',
730 self.map_mode = 'protections'
731 elif self.mode.name != 'edit':
732 self.map_mode = 'terrain + things'
733 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
734 self.explorer = YX(self.game.player.position.y,
735 self.game.player.position.x)
736 if self.mode.is_single_char_entry:
737 self.show_help = True
738 if len(self.mode.intro_msg) > 0:
739 self.log_msg(self.mode.intro_msg)
740 if self.mode.name == 'login':
742 self.send('LOGIN ' + quote(self.login_name))
744 self.log_msg('@ enter username')
745 elif self.mode.name == 'take_thing':
746 self.log_msg('Portable things in reach for pick-up:')
748 'HERE': YX(0, 0), 'LEFT': YX(0, -1), 'RIGHT': YX(0, 1)
750 if type(self.game.map_geometry) == MapGeometrySquare:
751 directed_moves['UP'] = YX(-1, 0)
752 directed_moves['DOWN'] = YX(1, 0)
753 elif type(self.game.map_geometry) == MapGeometryHex:
754 if self.game.player.position.y % 2:
755 directed_moves['UPLEFT'] = YX(-1, 0)
756 directed_moves['UPRIGHT'] = YX(-1, 1)
757 directed_moves['DOWNLEFT'] = YX(1, 0)
758 directed_moves['DOWNRIGHT'] = YX(1, 1)
760 directed_moves['UPLEFT'] = YX(-1, -1)
761 directed_moves['UPRIGHT'] = YX(-1, 0)
762 directed_moves['DOWNLEFT'] = YX(1, -1)
763 directed_moves['DOWNRIGHT'] = YX(1, 0)
765 for direction in directed_moves:
766 move = directed_moves[direction]
767 select_range[direction] = self.game.player.position + move
768 self.selectables = []
770 for direction in select_range:
771 for t in [t for t in self.game.things
772 if t.portable and t.position == select_range[direction]]:
773 self.selectables += [t.id_]
774 directions += [direction]
775 if len(self.selectables) == 0:
776 return fail('nothing to pick-up')
778 for i in range(len(self.selectables)):
779 t = self.game.get_thing(self.selectables[i])
780 self.log_msg('%s %s: %s' % (i, directions[i],
781 self.get_thing_info(t)))
782 elif self.mode.name == 'drop_thing':
783 self.log_msg('Direction to drop thing to:')
785 ['HERE'] + list(self.game.tui.movement_keys.values())
786 for i in range(len(self.selectables)):
787 self.log_msg(str(i) + ': ' + self.selectables[i])
788 elif self.mode.name == 'enter_design':
789 if self.game.player.carrying.type_ == 'Hat':
790 self.log_msg('@ The design you enter must be %s lines of max %s '
791 'characters width each'
792 % (self.game.player.carrying.design[0].y,
793 self.game.player.carrying.design[0].x))
794 self.log_msg('@ Legal characters: ' + self.game.players_hat_chars)
795 self.log_msg('@ (Eat cookies to extend the ASCII characters available for drawing.)')
797 self.log_msg('@ Width of first line determines maximum width for remaining design')
798 self.log_msg('@ Finish design by entering an empty line (multiple space characters do not count as empty)')
799 elif self.mode.name == 'command_thing':
800 self.send('TASK:COMMAND ' + quote('HELP'))
801 elif self.mode.name == 'control_pw_pw':
802 self.log_msg('@ enter protection password for "%s":' % self.tile_control_char)
803 elif self.mode.name == 'control_tile_draw':
804 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']))
806 self.restore_input_values()
808 def set_default_colors(self):
809 curses.init_color(1, 1000, 1000, 1000)
810 curses.init_color(2, 0, 0, 0)
811 self.do_refresh = True
813 def set_random_colors(self):
817 return int(offset + random.random()*375)
819 curses.init_color(1, rand(625), rand(625), rand(625))
820 curses.init_color(2, rand(0), rand(0), rand(0))
821 self.do_refresh = True
825 return self.info_cached
826 pos_i = self.explorer.y * self.game.map_geometry.size.x + self.explorer.x
828 if len(self.game.fov) > pos_i and self.game.fov[pos_i] != '.':
829 info_to_cache += 'outside field of view'
831 for t in self.game.things:
832 if t.position == self.explorer:
833 info_to_cache += '%s' % self.get_thing_info(t, True)
834 terrain_char = self.game.map_content[pos_i]
836 if terrain_char in self.game.terrains:
837 terrain_desc = self.game.terrains[terrain_char]
838 info_to_cache += 'TERRAIN: %s (%s' % (terrain_char,
840 protection = self.game.map_control_content[pos_i]
841 if protection != '.':
842 info_to_cache += '/protection:%s' % protection
843 info_to_cache += ')\n'
844 if self.explorer in self.game.portals:
845 info_to_cache += 'PORTAL: ' +\
846 self.game.portals[self.explorer] + '\n'
847 if self.explorer in self.game.annotations:
848 info_to_cache += 'ANNOTATION: ' +\
849 self.game.annotations[self.explorer]
850 self.info_cached = info_to_cache
851 return self.info_cached
853 def get_thing_info(self, t, detailed=False):
857 info += self.game.thing_types[t.type_]
858 if hasattr(t, 'thing_char'):
860 if hasattr(t, 'name'):
861 info += ': %s' % t.name
862 info += ' (%s' % t.type_
863 if hasattr(t, 'installed'):
865 if t.type_ == 'Bottle':
866 if t.thing_char == '_':
868 elif t.thing_char == '~':
871 protection = t.protection
872 if protection != '.':
873 info += '/protection:%s' % protection
875 if hasattr(t, 'hat') or hasattr(t, 'face'):
876 info += '----------\n'
877 if hasattr(t, 'hat'):
878 info += '| %s |\n' % t.hat[0:6]
879 info += '| %s |\n' % t.hat[6:12]
880 info += '| %s |\n' % t.hat[12:18]
881 if hasattr(t, 'face'):
882 info += '| %s |\n' % t.face[0:6]
883 info += '| %s |\n' % t.face[6:12]
884 info += '| %s |\n' % t.face[12:18]
885 info += '----------\n'
886 if hasattr(t, 'design'):
887 line_length = t.design[0].x
889 for i in range(t.design[0].y):
890 start = i * line_length
891 end = (i + 1) * line_length
892 lines += [t.design[1][start:end]]
893 info += '-' * (line_length + 4) + '\n'
895 info += '| %s |\n' % line
896 info += '-' * (line_length + 4) + '\n'
901 def loop(self, stdscr):
904 def safe_addstr(y, x, line):
905 if y < self.size.y - 1 or x + len(line) < self.size.x:
906 stdscr.addstr(y, x, line, curses.color_pair(1))
907 else: # workaround to <https://stackoverflow.com/q/7063128>
908 cut_i = self.size.x - x - 1
910 last_char = line[cut_i]
911 stdscr.addstr(y, self.size.x - 2, last_char, curses.color_pair(1))
912 stdscr.insstr(y, self.size.x - 2, ' ')
913 stdscr.addstr(y, x, cut, curses.color_pair(1))
915 def handle_input(msg):
916 command, args = self.parser.parse(msg)
919 def task_action_on(action):
920 return action_tasks[action] in self.game.tasks
922 def msg_into_lines_of_width(msg, width):
926 for i in range(len(msg)):
927 if x >= width or msg[i] == "\n":
939 def reset_screen_size():
940 self.size = YX(*stdscr.getmaxyx())
941 self.size = self.size - YX(self.size.y % 4, 0)
942 self.size = self.size - YX(0, self.size.x % 4)
943 self.left_window_width = min(52, int(self.size.x / 2))
944 self.right_window_width = self.size.x - self.left_window_width
946 def recalc_input_lines():
947 if not self.mode.has_input_prompt:
948 self.input_lines = []
950 self.input_lines = msg_into_lines_of_width(input_prompt
952 self.right_window_width)
954 def move_explorer(direction):
955 target = self.game.map_geometry.move_yx(self.explorer, direction)
957 self.info_cached = None
958 self.explorer = target
960 self.send_tile_control_command()
966 for line in self.log:
967 lines += msg_into_lines_of_width(line, self.right_window_width)
970 max_y = self.size.y - len(self.input_lines)
971 for i in range(len(lines)):
972 if (i >= max_y - height_header):
974 safe_addstr(max_y - i - 1, self.left_window_width, lines[i])
977 info = 'MAP VIEW: %s\n%s' % (self.map_mode, self.get_info())
978 lines = msg_into_lines_of_width(info, self.right_window_width)
980 for i in range(len(lines)):
981 y = height_header + i
982 if y >= self.size.y - len(self.input_lines):
984 safe_addstr(y, self.left_window_width, lines[i])
987 y = self.size.y - len(self.input_lines)
988 for i in range(len(self.input_lines)):
989 safe_addstr(y, self.left_window_width, self.input_lines[i])
993 stats = 'ENERGY: %s BLADDER: %s' % (self.game.energy,
994 self.game.bladder_pressure)
995 safe_addstr(0, self.left_window_width, stats)
998 help = "hit [%s] for help" % self.keys['help']
999 if self.mode.has_input_prompt:
1000 help = "enter /help for help"
1001 safe_addstr(1, self.left_window_width,
1002 'MODE: %s – %s' % (self.mode.short_desc, help))
1005 if (not self.game.turn_complete) and len(self.map_lines) == 0:
1007 if self.game.turn_complete:
1008 map_lines_split = []
1009 for y in range(self.game.map_geometry.size.y):
1010 start = self.game.map_geometry.size.x * y
1011 end = start + self.game.map_geometry.size.x
1012 if self.map_mode == 'protections':
1013 map_lines_split += [[c + ' ' for c
1014 in self.game.map_control_content[start:end]]]
1016 map_lines_split += [[c + ' ' for c
1017 in self.game.map_content[start:end]]]
1018 if self.map_mode == 'terrain + annotations':
1019 for p in self.game.annotations:
1020 map_lines_split[p.y][p.x] = 'A '
1021 elif self.map_mode == 'terrain + things':
1022 for p in self.game.portals.keys():
1023 original = map_lines_split[p.y][p.x]
1024 map_lines_split[p.y][p.x] = original[0] + 'P'
1027 def draw_thing(t, used_positions):
1028 symbol = self.game.thing_types[t.type_]
1030 if hasattr(t, 'thing_char'):
1031 meta_char = t.thing_char
1032 if t.position in used_positions:
1034 if hasattr(t, 'carrying') and t.carrying:
1036 map_lines_split[t.position.y][t.position.x] = symbol + meta_char
1037 used_positions += [t.position]
1039 for t in [t for t in self.game.things if t.type_ != 'Player']:
1040 draw_thing(t, used_positions)
1041 for t in [t for t in self.game.things if t.type_ == 'Player']:
1042 draw_thing(t, used_positions)
1043 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
1044 map_lines_split[self.explorer.y][self.explorer.x] = '??'
1045 elif self.map_mode != 'terrain + things':
1046 map_lines_split[self.game.player.position.y]\
1047 [self.game.player.position.x] = '??'
1049 if type(self.game.map_geometry) == MapGeometryHex:
1051 for line in map_lines_split:
1052 self.map_lines += [indent * ' ' + ''.join(line)]
1053 indent = 0 if indent else 1
1055 for line in map_lines_split:
1056 self.map_lines += [''.join(line)]
1057 window_center = YX(int(self.size.y / 2),
1058 int(self.left_window_width / 2))
1059 center = self.game.player.position
1060 if self.mode.shows_info or self.mode.name == 'control_tile_draw':
1061 center = self.explorer
1062 center = YX(center.y, center.x * 2)
1063 self.offset = center - window_center
1064 if type(self.game.map_geometry) == MapGeometryHex and self.offset.y % 2:
1065 self.offset += YX(0, 1)
1066 term_y = max(0, -self.offset.y)
1067 term_x = max(0, -self.offset.x)
1068 map_y = max(0, self.offset.y)
1069 map_x = max(0, self.offset.x)
1070 while term_y < self.size.y and map_y < len(self.map_lines):
1071 to_draw = self.map_lines[map_y][map_x:self.left_window_width + self.offset.x]
1072 safe_addstr(term_y, term_x, to_draw)
1076 def draw_face_popup():
1077 t = self.game.get_thing(self.draw_face)
1078 if not t or not hasattr(t, 'face'):
1079 self.draw_face = False
1082 start_x = self.left_window_width - 10
1083 def draw_body_part(body_part, end_y):
1084 safe_addstr(end_y - 3, start_x, '----------')
1085 safe_addstr(end_y - 2, start_x, '| ' + body_part[0:6] + ' |')
1086 safe_addstr(end_y - 1, start_x, '| ' + body_part[6:12] + ' |')
1087 safe_addstr(end_y, start_x, '| ' + body_part[12:18] + ' |')
1089 if hasattr(t, 'face'):
1090 draw_body_part(t.face, self.size.y - 3)
1091 if hasattr(t, 'hat'):
1092 draw_body_part(t.hat, self.size.y - 6)
1093 safe_addstr(self.size.y - 2, start_x, '----------')
1096 name = name[:6] + '…'
1097 safe_addstr(self.size.y - 1, start_x,
1098 '@%s:%s' % (t.thing_char, name))
1101 content = "%s help\n\n%s\n\n" % (self.mode.short_desc,
1102 self.mode.help_intro)
1103 if len(self.mode.available_actions) > 0:
1104 content += "Available actions:\n"
1105 for action in self.mode.available_actions:
1106 if action in action_tasks:
1107 if action_tasks[action] not in self.game.tasks:
1109 if action == 'move_explorer':
1111 if action == 'move':
1112 key = ','.join(self.movement_keys)
1114 key = self.keys[action]
1115 content += '[%s] – %s\n' % (key, action_descriptions[action])
1117 content += self.mode.list_available_modes(self)
1118 for i in range(self.size.y):
1120 self.left_window_width * (not self.mode.has_input_prompt),
1121 ' ' * self.left_window_width)
1123 for line in content.split('\n'):
1124 lines += msg_into_lines_of_width(line, self.right_window_width)
1125 for i in range(len(lines)):
1126 if i >= self.size.y:
1129 self.left_window_width * (not self.mode.has_input_prompt),
1134 stdscr.bkgd(' ', curses.color_pair(1))
1135 recalc_input_lines()
1136 if self.mode.has_input_prompt:
1138 if self.mode.shows_info:
1143 if not self.mode.is_intro:
1148 if self.draw_face and self.mode.name in {'chat', 'play'}:
1151 def pick_selectable(task_name):
1153 i = int(self.input_)
1154 if i < 0 or i >= len(self.selectables):
1155 self.log_msg('? invalid index, aborted')
1157 self.send('TASK:%s %s' % (task_name, self.selectables[i]))
1159 self.log_msg('? invalid index, aborted')
1161 self.switch_mode('play')
1163 def enter_ascii_art(command, height, width,
1164 with_pw=False, with_size=False):
1165 if with_size and self.ascii_draw_stage == 0:
1166 width = len(self.input_)
1168 self.log_msg('? input too long, must be max 36; try again')
1169 # TODO: move max width mechanism server-side
1171 old_size = self.game.player.carrying.design[0]
1172 if width != old_size.x:
1173 # TODO: save remaining design?
1174 self.game.player.carrying.design[1] = ''
1175 self.game.player.carrying.design[0] = YX(old_size.y, width)
1176 elif len(self.input_) > width:
1177 self.log_msg('? input too long, '
1178 'must be max %s; try again' % width)
1180 self.log_msg(' ' + self.input_)
1181 if with_size and self.input_ in {'', ' '}\
1182 and self.ascii_draw_stage > 0:
1183 height = self.ascii_draw_stage
1186 height = self.ascii_draw_stage + 2
1187 if len(self.input_) < width:
1188 self.input_ += ' ' * (width - len(self.input_))
1189 self.full_ascii_draw += self.input_
1191 old_size = self.game.player.carrying.design[0]
1192 self.game.player.carrying.design[0] = YX(height, old_size.x)
1193 self.ascii_draw_stage += 1
1194 if self.ascii_draw_stage < height:
1195 self.restore_input_values()
1197 if with_pw and with_size:
1198 self.send('%s_SIZE %s %s' % (command, YX(height, width),
1199 quote(self.password)))
1201 self.send('%s %s %s' % (command, quote(self.full_ascii_draw),
1202 quote(self.password)))
1204 self.send('%s %s' % (command, quote(self.full_ascii_draw)))
1205 self.full_ascii_draw = ""
1206 self.ascii_draw_stage = 0
1208 self.switch_mode('edit')
1210 action_descriptions = {
1212 'flatten': 'flatten surroundings',
1213 'teleport': 'teleport',
1214 'take_thing': 'pick up thing',
1215 'drop_thing': 'drop thing',
1216 'toggle_map_mode': 'toggle map view',
1217 'toggle_tile_draw': 'toggle protection character drawing',
1218 'install': '(un-)install',
1219 'wear': '(un-)wear',
1220 'door': 'open/close',
1221 'consume': 'consume',
1227 'flatten': 'FLATTEN_SURROUNDINGS',
1228 'take_thing': 'PICK_UP',
1229 'drop_thing': 'DROP',
1231 'install': 'INSTALL',
1234 'command': 'COMMAND',
1235 'consume': 'INTOXICATE',
1240 curses.curs_set(False) # hide cursor
1241 curses.start_color()
1242 self.set_default_colors()
1243 curses.init_pair(1, 1, 2)
1246 self.explorer = YX(0, 0)
1248 store_widechar = False
1250 interval = datetime.timedelta(seconds=5)
1251 last_ping = datetime.datetime.now() - interval
1253 if self.disconnected and self.force_instant_connect:
1254 self.force_instant_connect = False
1256 now = datetime.datetime.now()
1257 if now - last_ping > interval:
1258 if self.disconnected:
1268 self.do_refresh = False
1271 msg = self.queue.get(block=False)
1276 key = stdscr.getkey()
1277 self.do_refresh = True
1278 except curses.error:
1283 # workaround for <https://stackoverflow.com/a/56390915>
1285 store_widechar = False
1286 key = bytes([195, keycode]).decode()
1288 store_widechar = True
1290 self.show_help = False
1291 self.draw_face = False
1292 if key == 'KEY_RESIZE':
1294 elif self.mode.has_input_prompt and key == 'KEY_BACKSPACE':
1295 self.input_ = self.input_[:-1]
1296 elif (((not self.mode.is_intro) and keycode == 27) # Escape
1297 or (self.mode.has_input_prompt and key == '\n'
1298 and self.input_ == ''\
1299 and self.mode.name in {'chat', 'command_thing',
1300 'take_thing', 'drop_thing',
1302 if self.mode.name not in {'chat', 'play', 'study', 'edit'}:
1303 self.log_msg('@ aborted')
1304 self.switch_mode('play')
1305 elif self.mode.has_input_prompt and key == '\n' and self.input_ == '/help':
1306 self.show_help = True
1308 self.restore_input_values()
1309 elif self.mode.has_input_prompt and key != '\n': # Return key
1311 max_length = self.right_window_width * self.size.y - len(input_prompt) - 1
1312 if len(self.input_) > max_length:
1313 self.input_ = self.input_[:max_length]
1314 elif key == self.keys['help'] and not self.mode.is_single_char_entry:
1315 self.show_help = True
1316 elif self.mode.name == 'login' and key == '\n':
1317 self.login_name = self.input_
1318 self.send('LOGIN ' + quote(self.input_))
1320 elif self.mode.name == 'enter_face' and key == '\n':
1321 enter_ascii_art('PLAYER_FACE', 3, 6)
1322 elif self.mode.name == 'enter_design' and key == '\n':
1323 if self.game.player.carrying.type_ == 'Hat':
1324 enter_ascii_art('THING_DESIGN',
1325 self.game.player.carrying.design[0].y,
1326 self.game.player.carrying.design[0].x, True)
1328 enter_ascii_art('THING_DESIGN',
1329 self.game.player.carrying.design[0].y,
1330 self.game.player.carrying.design[0].x,
1332 elif self.mode.name == 'take_thing' and key == '\n':
1333 pick_selectable('PICK_UP')
1334 elif self.mode.name == 'drop_thing' and key == '\n':
1335 pick_selectable('DROP')
1336 elif self.mode.name == 'command_thing' and key == '\n':
1337 self.send('TASK:COMMAND ' + quote(self.input_))
1339 elif self.mode.name == 'control_pw_pw' and key == '\n':
1340 if self.input_ == '':
1341 self.log_msg('@ aborted')
1343 self.send('SET_MAP_CONTROL_PASSWORD ' + quote(self.tile_control_char) + ' ' + quote(self.input_))
1344 self.log_msg('@ sent new password for protection character "%s"' % self.tile_control_char)
1345 self.switch_mode('admin')
1346 elif self.mode.name == 'password' and key == '\n':
1347 if self.input_ == '':
1349 self.password = self.input_
1350 self.switch_mode('edit')
1351 elif self.mode.name == 'admin_enter' and key == '\n':
1352 self.send('BECOME_ADMIN ' + quote(self.input_))
1353 self.switch_mode('play')
1354 elif self.mode.name == 'control_pw_type' and key == '\n':
1355 if len(self.input_) != 1:
1356 self.log_msg('@ entered non-single-char, therefore aborted')
1357 self.switch_mode('admin')
1359 self.tile_control_char = self.input_
1360 self.switch_mode('control_pw_pw')
1361 elif self.mode.name == 'admin_thing_protect' and key == '\n':
1362 if len(self.input_) != 1:
1363 self.log_msg('@ entered non-single-char, therefore aborted')
1365 self.send('THING_PROTECTION %s' % (quote(self.input_)))
1366 self.log_msg('@ sent new protection character for thing')
1367 self.switch_mode('admin')
1368 elif self.mode.name == 'control_tile_type' and key == '\n':
1369 if len(self.input_) != 1:
1370 self.log_msg('@ entered non-single-char, therefore aborted')
1371 self.switch_mode('admin')
1373 self.tile_control_char = self.input_
1374 self.switch_mode('control_tile_draw')
1375 elif self.mode.name == 'chat' and key == '\n':
1376 if self.input_ == '':
1378 if self.input_[0] == '/':
1379 if self.input_.startswith('/nick'):
1380 tokens = self.input_.split(maxsplit=1)
1381 if len(tokens) == 2:
1382 self.send('NICK ' + quote(tokens[1]))
1384 self.log_msg('? need login name')
1386 self.log_msg('? unknown command')
1388 self.send('ALL ' + quote(self.input_))
1390 elif self.mode.name == 'name_thing' and key == '\n':
1391 if self.input_ == '':
1393 self.send('THING_NAME %s %s' % (quote(self.input_),
1394 quote(self.password)))
1395 self.switch_mode('edit')
1396 elif self.mode.name == 'annotate' and key == '\n':
1397 if self.input_ == '':
1399 self.send('ANNOTATE %s %s %s' % (self.explorer, quote(self.input_),
1400 quote(self.password)))
1401 self.switch_mode('edit')
1402 elif self.mode.name == 'portal' and key == '\n':
1403 if self.input_ == '':
1405 self.send('PORTAL %s %s %s' % (self.explorer, quote(self.input_),
1406 quote(self.password)))
1407 self.switch_mode('edit')
1408 elif self.mode.name == 'study':
1409 if self.mode.mode_switch_on_key(self, key):
1411 elif key == self.keys['toggle_map_mode']:
1412 self.toggle_map_mode()
1413 elif key in self.movement_keys:
1414 move_explorer(self.movement_keys[key])
1415 elif self.mode.name == 'play':
1416 if self.mode.mode_switch_on_key(self, key):
1418 elif key == self.keys['door'] and task_action_on('door'):
1419 self.send('TASK:DOOR')
1420 elif key == self.keys['consume'] and task_action_on('consume'):
1421 self.send('TASK:INTOXICATE')
1422 elif key == self.keys['wear'] and task_action_on('wear'):
1423 self.send('TASK:WEAR')
1424 elif key == self.keys['spin'] and task_action_on('spin'):
1425 self.send('TASK:SPIN')
1426 elif key == self.keys['dance'] and task_action_on('dance'):
1427 self.send('TASK:DANCE')
1428 elif key == self.keys['teleport']:
1429 if self.game.player.position in self.game.portals:
1430 self.host = self.game.portals[self.game.player.position]
1434 self.log_msg('? not standing on portal')
1435 elif key in self.movement_keys and task_action_on('move'):
1436 self.send('TASK:MOVE ' + self.movement_keys[key])
1437 elif self.mode.name == 'write':
1438 self.send('TASK:WRITE %s %s' % (key, quote(self.password)))
1439 self.switch_mode('edit')
1440 elif self.mode.name == 'control_tile_draw':
1441 if self.mode.mode_switch_on_key(self, key):
1443 elif key in self.movement_keys:
1444 move_explorer(self.movement_keys[key])
1445 elif key == self.keys['toggle_tile_draw']:
1446 self.tile_draw = False if self.tile_draw else True
1447 elif self.mode.name == 'admin':
1448 if self.mode.mode_switch_on_key(self, key):
1450 elif key == self.keys['toggle_map_mode']:
1451 self.toggle_map_mode()
1452 elif key in self.movement_keys and task_action_on('move'):
1453 self.send('TASK:MOVE ' + self.movement_keys[key])
1454 elif self.mode.name == 'edit':
1455 if self.mode.mode_switch_on_key(self, key):
1457 elif key == self.keys['flatten'] and task_action_on('flatten'):
1458 self.send('TASK:FLATTEN_SURROUNDINGS ' + quote(self.password))
1459 elif key == self.keys['install'] and task_action_on('install'):
1460 self.send('TASK:INSTALL %s' % quote(self.password))
1461 elif key == self.keys['toggle_map_mode']:
1462 self.toggle_map_mode()
1463 elif key in self.movement_keys and task_action_on('move'):
1464 self.send('TASK:MOVE ' + self.movement_keys[key])
1466 if len(sys.argv) != 2:
1467 raise ArgError('wrong number of arguments, need game host')