""" Player (OOC) commands. These are stored on the Player object and self.caller is thus always a Player, not an Object/Character. These commands go in the PlayerCmdset and are accessible also when puppeting a Character (although with lower priority) These commands use the MuxCommandOOC parent that makes sure to setup caller correctly. The self.character can be used to access the character when these commands are triggered with a connected character (such as the case of the @ooc command), it is None if we are OOC. Note that under MULTISESSION_MODE=2, Player- commands should use self.msg() and similar methods to reroute returns to the correct method. Otherwise all text will be returned to all connected sessions. """ import time from django.conf import settings from src.server.sessionhandler import SESSIONS from src.commands.default.muxcommand import MuxPlayerCommand from src.utils import utils, create, search, prettytable from settings import MAX_NR_CHARACTERS, MULTISESSION_MODE # limit symbol import for API __all__ = ("CmdOOCLook", "CmdIC", "CmdOOC", "CmdPassword", "CmdQuit", "CmdCharCreate", "CmdEncoding", "CmdSessions", "CmdWho", "CmdColorTest", "CmdQuell") # force max nr chars to 1 if mode is 0 or 1 MAX_NR_CHARACTERS = MULTISESSION_MODE < 2 and 1 or MAX_NR_CHARACTERS BASE_PLAYER_TYPECLASS = settings.BASE_PLAYER_TYPECLASS PERMISSION_HIERARCHY = settings.PERMISSION_HIERARCHY PERMISSION_HIERARCHY_LOWER = [perm.lower() for perm in PERMISSION_HIERARCHY] class CmdOOCLook(MuxPlayerCommand): """ ooc look Usage: look Look in the ooc state. """ #This is an OOC version of the look command. Since a #Player doesn't have an in-game existence, there is no #concept of location or "self". If we are controlling #a character, pass control over to normal look. key = "look" aliases = ["l", "ls"] locks = "cmd:all()" help_category = "General" def look_target(self): "Hook method for when an argument is given." player = self.caller key = self.args.lower() chars = dict((utils.to_str(char.key.lower()), char) for char in player.db._playable_characters) looktarget = chars.get(key) if looktarget: self.msg(looktarget.return_appearance(player)) else: self.msg("No such character.") return def no_look_target(self): "Hook method for default look without a specified target" # caller is always a player at this point. player = self.caller sessid = self.sessid # get all our characters and sessions characters = player.db._playable_characters sessions = player.get_all_sessions() is_su = player.is_superuser # text shown when looking in the ooc area string = "Account {g%s{n (you are Out-of-Character)" % (player.key) nsess = len(sessions) string += nsess == 1 and "\n\n{wConnected session:{n" or "\n\n{wConnected sessions (%i):{n" % nsess for isess, sess in enumerate(sessions): csessid = sess.sessid addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address)) string += "\n %s %s" % (sessid == csessid and "{w%s{n" % (isess + 1) or (isess + 1), addr) string += "\n\n {whelp{n - more commands" string += "\n {wooc {n - talk on public channel" if is_su or len(characters) < MAX_NR_CHARACTERS: if not characters: string += "\n\n You don't have any character yet. See {whelp @charcreate{n for creating one." else: string += "\n {w@charcreate [=description]{n - create new character" if characters: string_s_ending = len(characters) > 1 and "s" or "" string += "\n {w@ic {n - enter the game ({w@ooc{n to get back here)" if is_su: string += "\n\nAvailable character%s (%i/unlimited):" % (string_s_ending, len(characters)) else: string += "\n\nAvailable character%s%s:" % (string_s_ending, MAX_NR_CHARACTERS > 1 and " (%i/%i)" % (len(characters), MAX_NR_CHARACTERS) or "") for char in characters: csessid = char.sessid if csessid: # character is already puppeted sess = player.get_session(csessid) sid = sess in sessions and sessions.index(sess) + 1 if sess and sid: string += "\n - {G%s{n [%s] (played by you in session %i)" % (char.key, ", ".join(char.permissions.all()), sid) else: string += "\n - {R%s{n [%s] (played by someone else)" % (char.key, ", ".join(char.permissions.all())) else: # character is "free to puppet" string += "\n - %s [%s]" % (char.key, ", ".join(char.permissions.all())) string = ("-" * 68) + "\n" + string + "\n" + ("-" * 68) self.msg(string) def func(self): "implement the ooc look command" if MULTISESSION_MODE < 2: # only one character allowed string = "You are out-of-character (OOC).\nUse {w@ic{n to get back into the game." self.msg(string) return if utils.inherits_from(self.caller, "src.objects.objects.Object"): # An object of some type is calling. Use default look instead. super(CmdOOCLook, self).func() elif self.args: self.look_target() else: self.no_look_target() class CmdCharCreate(MuxPlayerCommand): """ Create a character Usage: @charcreate [= desc] Create a new character, optionally giving it a description. You may use upper-case letters in the name - you will nevertheless always be able to access your character using lower-case letters if you want. """ key = "@charcreate" locks = "cmd:all()" help_category = "General" def func(self): "create the new character" player = self.caller if not self.args: self.msg("Usage: @charcreate [= description]") return key = self.lhs desc = self.rhs if not player.is_superuser and \ (player.db._playable_characters and len(player.db._playable_characters) >= MAX_NR_CHARACTERS): self.msg("You may only create a maximum of %i characters." % MAX_NR_CHARACTERS) return # create the character from src.objects.models import ObjectDB default_home = ObjectDB.objects.get_id(settings.CHARACTER_DEFAULT_HOME) typeclass = settings.BASE_CHARACTER_TYPECLASS permissions = settings.PERMISSION_PLAYER_DEFAULT new_character = create.create_object(typeclass, key=key, location=default_home, home=default_home, permissions=permissions) # only allow creator (and immortals) to puppet this char new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % (new_character.id, player.id)) player.db._playable_characters.append(new_character) if desc: new_character.db.desc = desc else: new_character.db.desc = "This is a Player." self.msg("Created new character %s. Use {w@ic %s{n to enter the game as this character." % (new_character.key, new_character.key)) class CmdIC(MuxPlayerCommand): """ Switch control to an object Usage: @ic Go in-character (IC) as a given Character. This will attempt to "become" a different object assuming you have the right to do so. Note that it's the PLAYER character that puppets characters/objects and which needs to have the correct permission! You cannot become an object that is already controlled by another player. In principle can be any in-game object as long as you the player have access right to puppet it. """ key = "@ic" locks = "cmd:all()" # must be all() or different puppeted objects won't be able to access it. aliases = "@puppet" help_category = "General" def func(self): """ Main puppet method """ player = self.caller sessid = self.sessid new_character = None if not self.args: new_character = player.db._last_puppet if not new_character: self.msg("Usage: @ic ") return if not new_character: # search for a matching character new_character = search.object_search(self.args) if new_character: new_character = new_character[0] else: self.msg("That is not a valid character choice.") return # permission checks if player.get_puppet(sessid) == new_character: self.msg("{RYou already act as {c%s{n." % new_character.name) return if new_character.player: # may not puppet an already puppeted character if new_character.sessid and new_character.player == player: # as a safeguard we allow "taking over chars from your own sessions. player.msg("{c%s{n{R is now acted from another of your sessions.{n" % (new_character.name), sessid=new_character.sessid) player.unpuppet_object(new_character.sessid) self.msg("Taking over {c%s{n from another of your sessions." % new_character.name) elif new_character.player != player and new_character.player.is_connected: self.msg("{c%s{r is already acted by another player.{n" % new_character.name) return if not new_character.access(player, "puppet"): # main acccess check self.msg("{rYou may not become %s.{n" % new_character.name) return if player.puppet_object(sessid, new_character): player.db._last_puppet = new_character else: self.msg("{rYou cannot become {C%s{n." % new_character.name) class CmdOOC(MuxPlayerCommand): """ go ooc Usage: @ooc Go out-of-character (OOC). This will leave your current character and put you in a incorporeal OOC state. """ key = "@ooc" locks = "cmd:all()" # this must be all(), or different puppeted objects won't be able to access it. aliases = "@unpuppet" help_category = "General" def func(self): "Implement function" player = self.caller sessid = self.sessid old_char = player.get_puppet(sessid) if not old_char: string = "You are already OOC." self.msg(string) return player.db._last_puppet = old_char # save location as if we were disconnecting from the game entirely. if old_char.location: old_char.location.msg_contents("%s has left the game." % old_char.key, exclude=[old_char]) # disconnect if player.unpuppet_object(sessid): self.msg("\n{GYou go OOC.{n\n") player.execute_cmd("look", sessid=sessid) else: raise RuntimeError("Could not unpuppet!") class CmdSessions(MuxPlayerCommand): """ check connected session(s) Usage: @sessions Lists the sessions currently connected to your account. """ key = "@sessions" locks = "cmd:all()" help_category = "General" def func(self): "Implement function" player = self.caller sessions = player.get_all_sessions() table = prettytable.PrettyTable(["{wsessid", "{wprotocol", "{whost", "{wpuppet/character", "{wlocation"]) for sess in sorted(sessions, key=lambda x:x.sessid): sessid = sess.sessid char = player.get_puppet(sessid) table.add_row([str(sessid), str(sess.protocol_key), type(sess.address)==tuple and sess.address[0] or sess.address, char and str(char) or "None", char and str(char.location) or "N/A"]) string = "{wYour current session(s):{n\n%s" % table self.msg(string) class CmdWho(MuxPlayerCommand): """ who Usage: who doing Shows who is currently online. Doing is an alias that limits info also for those with all permissions. """ key = "who" aliases = "doing" locks = "cmd:all()" def func(self): """ Get all connected players by polling session. """ player = self.caller session_list = SESSIONS.get_sessions() session_list = sorted(session_list, key=lambda o: o.player.key) if self.cmdstring == "doing": show_session_data = False else: show_session_data = player.check_permstring("Immortals") or player.check_permstring("Wizards") nplayers = (SESSIONS.player_count()) if show_session_data: table = prettytable.PrettyTable(["{wPlayer Name","{wOn for", "{wIdle", "{wRoom", "{wCmds", "{wProtocol", "{wHost"]) for session in session_list: if not session.logged_in: continue delta_cmd = time.time() - session.cmd_last_visible delta_conn = time.time() - session.conn_time plr_pobject = session.get_puppet() plr_pobject = plr_pobject or session.get_player() table.add_row([utils.crop(plr_pobject.name, width=25), utils.time_format(delta_conn, 0), utils.time_format(delta_cmd, 1), hasattr(plr_pobject, "location") and plr_pobject.location.key or "None", session.cmd_total, session.protocol_key, isinstance(session.address, tuple) and session.address[0] or session.address]) else: table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{wIdle"]) for session in session_list: if not session.logged_in: continue delta_cmd = time.time() - session.cmd_last_visible delta_conn = time.time() - session.conn_time plr_pobject = session.get_puppet() plr_pobject = plr_pobject or session.get_player() table.add_row([utils.crop(plr_pobject.name, width=25), utils.time_format(delta_conn, 0), utils.time_format(delta_cmd, 1)]) isone = nplayers == 1 string = "{wPlayers:{n\n%s\n%s unique account%s logged in." % (table, "One" if isone else nplayers, "" if isone else "s") self.msg(string) class CmdEncoding(MuxPlayerCommand): """ encoding - set a custom text encoding Usage: @encoding/switches [] Switches: clear - clear your custom encoding This sets the text encoding for communicating with Evennia. This is mostly an issue only if you want to use non-ASCII characters (i.e. letters/symbols not found in English). If you see that your characters look strange (or you get encoding errors), you should use this command to set the server encoding to be the same used in your client program. Common encodings are utf-8 (default), latin-1, ISO-8859-1 etc. If you don't submit an encoding, the current encoding will be displayed instead. """ key = "@encoding" aliases = "@encode" locks = "cmd:all()" def func(self): """ Sets the encoding. """ player = self.caller if 'clear' in self.switches: # remove customization old_encoding = player.db.encoding if old_encoding: string = "Your custom text encoding ('%s') was cleared." % old_encoding else: string = "No custom encoding was set." del player.db.encoding elif not self.args: # just list the encodings supported pencoding = player.db.encoding string = "" if pencoding: string += "Default encoding: {g%s{n (change with {w@encoding {n)" % pencoding encodings = settings.ENCODINGS if encodings: string += "\nServer's alternative encodings (tested in this order):\n {g%s{n" % ", ".join(encodings) if not string: string = "No encodings found." else: # change encoding old_encoding = player.db.encoding encoding = self.args player.db.encoding = encoding string = "Your custom text encoding was changed from '%s' to '%s'." % (old_encoding, encoding) self.msg(string.strip()) class CmdPassword(MuxPlayerCommand): """ @password - set your password Usage: @password = Changes your password. Make sure to pick a safe one. """ key = "@password" locks = "cmd:all()" def func(self): "hook function." player = self.caller if not self.rhs: self.msg("Usage: @password = ") return oldpass = self.lhslist[0] # this is already stripped by parse() newpass = self.rhslist[0] # '' if not player.check_password(oldpass): self.msg("The specified old password isn't correct.") elif len(newpass) < 3: self.msg("Passwords must be at least three characters long.") else: player.set_password(newpass) player.save() self.msg("Password changed.") class CmdQuit(MuxPlayerCommand): """ quit Usage: @quit Switch: all - disconnect all connected sessions Gracefully disconnect your current session from the game. Use the /all switch to disconnect from all sessions. """ key = "@quit" locks = "cmd:all()" def func(self): "hook function" player = self.caller if 'all' in self.switches: player.msg("{RQuitting{n all sessions. Hope to see you soon again.", sessid=self.sessid) for session in player.get_all_sessions(): player.disconnect_session_from_player(session.sessid) else: nsess = len(player.get_all_sessions()) if nsess == 2: player.msg("{RQuitting{n. One session is still connected.", sessid=self.sessid) elif nsess > 2: player.msg("{RQuitting{n. %i session are still connected." % (nsess-1), sessid=self.sessid) else: # we are quitting the last available session player.msg("{RQuitting{n. Hope to see you soon again.", sessid=self.sessid) player.disconnect_session_from_player(self.sessid) class CmdColorTest(MuxPlayerCommand): """ testing colors Usage: @color ansi|xterm256 Print a color map along with in-mud color codes, while testing what is supported in your client. Choices are 16-color ansi (supported in most muds) or the 256-color xterm256 standard. No checking is done to determine your client supports color - if not you will see rubbish appear. """ key = "@color" locks = "cmd:all()" help_category = "General" def table_format(self, table): """ Helper method to format the ansi/xterm256 tables. Takes a table of columns [[val,val,...],[val,val,...],...] """ if not table: return [[]] extra_space = 1 max_widths = [max([len(str(val)) for val in col]) for col in table] ftable = [] for irow in range(len(table[0])): ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space for icol, col in enumerate(table)]) return ftable def func(self): "Show color tables" if self.args.startswith("a"): # show ansi 16-color table from src.utils import ansi ap = ansi.ANSI_PARSER # ansi colors # show all ansi color-related codes col1 = ["%s%s{n" % (code, code.replace("{","{{")) for code, _ in ap.ext_ansi_map[:-1]] hi = "%ch" col2 = ["%s%s{n" % (code, code.replace("%", "%%")) for code, _ in ap.mux_ansi_map[3:-2]] col3 = ["%s%s{n" % (hi+code, (hi+code).replace("%", "%%")) for code, _ in ap.mux_ansi_map[3:-2]] table = utils.format_table([col1, col2, col3], extra_space=1) string = "ANSI colors:" for row in table: string += "\n" + "".join(row) #print string self.msg(string) self.msg("({{X and %%cx are black-on-black\n %%r - return, %%t - tab, %%b - space)") elif self.args.startswith("x"): # show xterm256 table table = [[],[],[],[],[],[],[],[],[],[],[],[]] for ir in range(6): for ig in range(6): for ib in range(6): # foreground table table[ir].append("%%c%i%i%i%s{n" % (ir,ig,ib, "{{%i%i%i" % (ir,ig,ib))) # background table table[6+ir].append("%%cb%i%i%i%%c%i%i%i%s{n" % (ir,ig,ib, 5-ir,5-ig,5-ib, "{{b%i%i%i" % (ir,ig,ib))) table = self.table_format(table) string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):" for row in table: string += "\n" + "".join(row) self.msg(string) self.msg("(e.g. %%c123 and %%cb123 also work)") else: # malformed input self.msg("Usage: @color ansi|xterm256") class CmdQuell(MuxPlayerCommand): """ Quelling permissions Usage: quell unquell Normally the permission level of the Player is used when puppeting a Character/Object to determine access. This command will switch the lock system to make use of the puppeted Object's permissions instead. This is useful mainly for testing. Hierarchical permission quelling only work downwards, thus a Player cannot use a higher-permission Character to escalate their permission level. Use the unquell command to revert back to normal operation. """ key = "@quell" aliases =["@unquell"] locks = "cmd:all()" help_category = "General" def _recache_locks(self, player): "Helper method to reset the lockhandler on an already puppeted object" if self.sessid: char = player.get_puppet(self.sessid) if char: # we are already puppeting an object. We need to reset the lock caches # (otherwise the superuser status change won't be visible until repuppet) char.locks.reset() player.locks.reset() def func(self): "Perform the command" player = self.caller permstr = player.is_superuser and " (superuser)" or " (%s)" % (", ".join(player.permissions.all())) if self.cmdstring == '@unquell': if not player.attributes.get('_quell'): self.msg("Already using normal Player permissions%s." % permstr) else: player.attributes.remove('_quell') self.msg("Player permissions%s restored." % permstr) else: if player.attributes.get('_quell'): self.msg("Already quelling Player%s permissions." % permstr) return player.attributes.add('_quell', True) self.msg("Quelling Player permissions%s. Use @unquell to get them back." % permstr) self._recache_locks(player)