def _init_command(cls, **kwargs): """ Helper command. Makes sure all data are stored as lowercase and do checking on all properties that should be in list form. Sets up locks to be more forgiving. This is used both by the metaclass and (optionally) at instantiation time. If kwargs are given, these are set as instance-specific properties on the command - but note that the Command instance is *re-used* on a given host object, so a kwarg value set on the instance will *remain* on the instance for subsequent uses of that Command on that particular object. """ for i in range(len(kwargs)): # used for dynamic creation of commands key, value = kwargs.popitem() setattr(cls, key, value) cls.key = cls.key.lower() if cls.aliases and not is_iter(cls.aliases): try: cls.aliases = [str(alias).strip().lower() for alias in cls.aliases.split(",")] except Exception: cls.aliases = [] cls.aliases = list(set(alias for alias in cls.aliases if alias and alias != cls.key)) # optimization - a set is much faster to match against than a list cls._matchset = set([cls.key] + cls.aliases) # optimization for looping over keys+aliases cls._keyaliases = tuple(cls._matchset) # by default we don't save the command between runs if not hasattr(cls, "save_for_next"): cls.save_for_next = False # pre-process locks as defined in class definition temp = [] if hasattr(cls, "permissions"): cls.locks = cls.permissions if not hasattr(cls, "locks"): # default if one forgets to define completely cls.locks = "cmd:all()" if "cmd:" not in cls.locks: cls.locks = "cmd:all();" + cls.locks for lockstring in cls.locks.split(";"): if lockstring and ":" not in lockstring: lockstring = "cmd:%s" % lockstring temp.append(lockstring) cls.lock_storage = ";".join(temp) if hasattr(cls, "arg_regex") and isinstance(cls.arg_regex, str): cls.arg_regex = re.compile(r"%s" % cls.arg_regex, re.I + re.UNICODE) if not hasattr(cls, "auto_help"): cls.auto_help = True if not hasattr(cls, "is_exit"): cls.is_exit = False if not hasattr(cls, "help_category"): cls.help_category = "general" cls.help_category = cls.help_category.lower()
def get_objs_with_key_or_alias(self, ostring, exact=True, candidates=None, typeclasses=None): """ Args: ostring (str): A search criterion. exact (bool, optional): Require exact match of ostring (still case-insensitive). If `False`, will do fuzzy matching using `evennia.utils.utils.string_partial_matching` algorithm. candidates (list): Only match among these candidates. typeclasses (list): Only match objects with typeclasses having thess path strings. Returns: matches (list): A list of matches of length 0, 1 or more. """ if not isinstance(ostring, basestring): if hasattr(ostring, "key"): ostring = ostring.key else: return [] if is_iter(candidates) and not len(candidates): # if candidates is an empty iterable there can be no matches # Exit early. return [] # build query objects candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj] cand_restriction = candidates != None and Q(pk__in=candidates_id) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() if exact: # exact match - do direct search return self.filter(cand_restriction & type_restriction & (Q(db_key__iexact=ostring) | Q(db_tags__db_key__iexact=ostring) & Q(db_tags__db_tagtype__iexact="alias"))).distinct() elif candidates: # fuzzy with candidates search_candidates = self.filter(cand_restriction & type_restriction) else: # fuzzy without supplied candidates - we select our own candidates search_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) | Q(db_tags__db_key__istartswith=ostring))).distinct() # fuzzy matching key_strings = search_candidates.values_list("db_key", flat=True).order_by("id") index_matches = string_partial_matching(key_strings, ostring, ret_index=True) if index_matches: # a match by key return [obj for ind, obj in enumerate(search_candidates) if ind in index_matches] else: # match by alias rather than by key search_candidates = search_candidates.filter(db_tags__db_tagtype__iexact="alias", db_tags__db_key__icontains=ostring) alias_strings = [] alias_candidates = [] #TODO create the alias_strings and alias_candidates lists more effiently? for candidate in search_candidates: for alias in candidate.aliases.all(): alias_strings.append(alias) alias_candidates.append(candidate) index_matches = string_partial_matching(alias_strings, ostring, ret_index=True) if index_matches: return [alias_candidates[ind] for ind in index_matches] return []
def _validate(data): "Helper function to convert data to AMP-safe (picketable) values" if isinstance(data, dict): newdict = {} for key, part in data.items(): newdict[key] = _validate(part) return newdict elif is_iter(data): return [_validate(part) for part in data] elif isinstance(data, (str, bytes)): data = _utf8(data) if _INLINEFUNC_ENABLED and not raw and isinstance( self, ServerSessionHandler): # only parse inlinefuncs on the outgoing path (sessionhandler->) data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session) return str(data) elif (hasattr(data, "id") and hasattr(data, "db_date_created") and hasattr(data, "__dbclass__")): # convert database-object to their string representation. return _validate(str(data)) else: return data
def _init_command(mcs, **kwargs): """ Helper command. Makes sure all data are stored as lowercase and do checking on all properties that should be in list form. Sets up locks to be more forgiving. This is used both by the metaclass and (optionally) at instantiation time. If kwargs are given, these are set as instance-specific properties on the command. """ for i in range(len(kwargs)): # used for dynamic creation of commands key, value = kwargs.popitem() setattr(mcs, key, value) mcs.key = mcs.key.lower() if mcs.aliases and not is_iter(mcs.aliases): try: mcs.aliases = [ str(alias).strip().lower() for alias in mcs.aliases.split(',') ] except Exception: mcs.aliases = [] mcs.aliases = list( set(alias for alias in mcs.aliases if alias and alias != mcs.key)) # optimization - a set is much faster to match against than a list mcs._matchset = set([mcs.key] + mcs.aliases) # optimization for looping over keys+aliases mcs._keyaliases = tuple(mcs._matchset) # by default we don't save the command between runs if not hasattr(mcs, "save_for_next"): mcs.save_for_next = False # pre-process locks as defined in class definition temp = [] if hasattr(mcs, 'permissions'): mcs.locks = mcs.permissions if not hasattr(mcs, 'locks'): # default if one forgets to define completely mcs.locks = "cmd:all()" if not "cmd:" in mcs.locks: mcs.locks = "cmd:all();" + mcs.locks for lockstring in mcs.locks.split(';'): if lockstring and not ':' in lockstring: lockstring = "cmd:%s" % lockstring temp.append(lockstring) mcs.lock_storage = ";".join(temp) if hasattr(mcs, 'arg_regex') and isinstance(mcs.arg_regex, basestring): mcs.arg_regex = re.compile(r"%s" % mcs.arg_regex, re.I) if not hasattr(mcs, "auto_help"): mcs.auto_help = True if not hasattr(mcs, 'is_exit'): mcs.is_exit = False if not hasattr(mcs, "help_category"): mcs.help_category = "general" mcs.help_category = mcs.help_category.lower()
def init_spawn_value(value, validator=None): """ Analyze the prototype value and produce a value useful at the point of spawning. Args: value (any): This can be: callable - will be called as callable() (callable, (args,)) - will be called as callable(*args) other - will be assigned depending on the variable type validator (callable, optional): If given, this will be called with the value to check and guarantee the outcome is of a given type. Returns: any (any): The (potentially pre-processed value to use for this prototype key) """ value = protfunc_parser(value) validator = validator if validator else lambda o: o if callable(value): return validator(value()) elif value and is_iter(value) and callable(value[0]): # a structure (callable, (args, )) args = value[1:] return validator(value[0](*make_iter(args))) else: return validator(value)
def session_from_sessid(self, sessid): """ Return session based on sessid, or None if not found """ if is_iter(sessid): return [self.sessions.get(sid) for sid in sessid if sid in self.sessions] return self.sessions.get(sessid)
def msg_exits(self, text=None, exclude=None, from_obj=None, **kwargs): """ Emit message to all objects in rooms this room connects to via exits. Args: text (str or tuple): Message to send. If a tuple, this should be on the valid OOB outmessage form `(message, {kwargs})`, where kwargs are optional data passed to the `text` outputfunc. exclude (list, optional): A list of objects not to send to. from_obj (Object, optional): An object designated as the "sender" of the message. See `DefaultObject.msg()` for more info. Kwargs: Keyword arguments will be passed on to `obj.msg()` for all messaged objects. """ # we also accept an outcommand on the form (message, {kwargs}) is_outcmd = text and is_iter(text) message = text[0] if is_outcmd else text outkwargs = text[1] if is_outcmd and len(text) > 1 else {} # Collect exit and entrance locations with no repeats. rooms = [exit.destination for exit in self.exits] rooms = list(set(rooms)) if self in rooms: rooms.remove(self) if exclude: exclude = make_iter(exclude) rooms = [room for room in rooms if room not in exclude] for room in rooms: room.msg_contents(text=(message, outkwargs), from_obj=from_obj, **kwargs)
def add(self, cmd): """ Add a command, a list of commands or a cmdset to this cmdset. Note that if cmd already exists in set, it will replace the old one (no priority checking etc at this point; this is often used to overload default commands). If cmd is another cmdset class or -instance, the commands of that command set is added to this one, as if they were part of the original cmdset definition. No merging or priority checks are made, rather later added commands will simply replace existing ones to make a unique set. """ if inherits_from(cmd, "evennia.commands.cmdset.CmdSet"): # cmd is a command set so merge all commands in that set # to this one. We raise a visible error if we created # an infinite loop (adding cmdset to itself somehow) try: cmd = self._instantiate(cmd) except RuntimeError: string = "Adding cmdset %(cmd)s to %(class)s lead to an " string += "infinite loop. When adding a cmdset to another, " string += "make sure they are not themself cyclically added to " string += "the new cmdset somewhere in the chain." raise RuntimeError( _(string) % { "cmd": cmd, "class": self.__class__ }) cmds = cmd.commands elif is_iter(cmd): cmds = [self._instantiate(c) for c in cmd] else: cmds = [self._instantiate(cmd)] commands = self.commands system_commands = self.system_commands for cmd in cmds: # add all commands if not hasattr(cmd, 'obj'): cmd.obj = self.cmdsetobj try: ic = commands.index(cmd) commands[ic] = cmd # replace except ValueError: commands.append(cmd) # extra run to make sure to avoid doublets self.commands = list(set(commands)) #print "In cmdset.add(cmd):", self.key, cmd # add system_command to separate list as well, # for quick look-up if cmd.key.startswith("__"): try: ic = system_commands.index(cmd) system_commands[ic] = cmd # replace except ValueError: system_commands.append(cmd)
def _init_command(mcs, **kwargs): """ Helper command. Makes sure all data are stored as lowercase and do checking on all properties that should be in list form. Sets up locks to be more forgiving. This is used both by the metaclass and (optionally) at instantiation time. If kwargs are given, these are set as instance-specific properties on the command. """ for i in range(len(kwargs)): # used for dynamic creation of commands key, value = kwargs.popitem() setattr(mcs, key, value) mcs.key = mcs.key.lower() if mcs.aliases and not is_iter(mcs.aliases): try: mcs.aliases = [str(alias).strip().lower() for alias in mcs.aliases.split(',')] except Exception: mcs.aliases = [] mcs.aliases = list(set(alias for alias in mcs.aliases if alias and alias != mcs.key)) # optimization - a set is much faster to match against than a list mcs._matchset = set([mcs.key] + mcs.aliases) # optimization for looping over keys+aliases mcs._keyaliases = tuple(mcs._matchset) # by default we don't save the command between runs if not hasattr(mcs, "save_for_next"): mcs.save_for_next = False # pre-process locks as defined in class definition temp = [] if hasattr(mcs, 'permissions'): mcs.locks = mcs.permissions if not hasattr(mcs, 'locks'): # default if one forgets to define completely mcs.locks = "cmd:all()" if not "cmd:" in mcs.locks: mcs.locks = "cmd:all();" + mcs.locks for lockstring in mcs.locks.split(';'): if lockstring and not ':' in lockstring: lockstring = "cmd:%s" % lockstring temp.append(lockstring) mcs.lock_storage = ";".join(temp) if hasattr(mcs, 'arg_regex') and isinstance(mcs.arg_regex, basestring): mcs.arg_regex = re.compile(r"%s" % mcs.arg_regex, re.I + re.UNICODE) if not hasattr(mcs, "auto_help"): mcs.auto_help = True if not hasattr(mcs, 'is_exit'): mcs.is_exit = False if not hasattr(mcs, "help_category"): mcs.help_category = "general" mcs.help_category = mcs.help_category.lower()
def sessions_from_puppet(self, puppet): """ Given a puppeted object, return all controlling sessions. """ sessid = puppet.sessid.get() if is_iter(sessid): return [self.sessions.get(sid) for sid in sessid if sid in self.sessions] return self.sessions.get(sessid)
def value_to_obj(value, force=True): "Always convert value(s) to Object, or None" stype = type(value) if is_iter(value): if stype == dict: return {value_to_obj_or_any(key): value_to_obj_or_any(val) for key, val in value.iter()} else: return stype([value_to_obj_or_any(val) for val in value]) return dbid_to_obj(value, ObjectDB)
def drill(obj, bucket): if isinstance(obj, dict): return bucket elif utils.is_iter(obj): for sub_obj in obj: bucket.extend(drill(sub_obj, [])) else: bucket.append(obj) return bucket
def do_mssp(self, option): """ Negotiate all the information. Args: option (Option): Not used. """ self.mssp_table = { # Required fields "NAME": settings.SERVERNAME, "PLAYERS": self.get_player_count, "UPTIME": self.get_uptime, "PORT": list(reversed(settings.TELNET_PORTS) ), # most important port should be last in list # Evennia auto-filled "CRAWL DELAY": "-1", "CODEBASE": utils.get_evennia_version(mode='pretty'), "FAMILY": "Custom", "ANSI": "1", "GMCP": "1" if settings.TELNET_OOB_ENABLED else "0", "ATCP": "0", "MCCP": "1", "MCP": "0", "MSDP": "1" if settings.TELNET_OOB_ENABLED else "0", "MSP": "0", "MXP": "1", "PUEBLO": "0", "SSL": "1" if settings.SSL_ENABLED else "0", "UTF-8": "1", "ZMP": "0", "VT100": "1", "XTERM 256 COLORS": "1", } # update the static table with the custom one if MSSPTable_CUSTOM: self.mssp_table.update(MSSPTable_CUSTOM) varlist = b'' for variable, value in self.mssp_table.items(): if callable(value): value = value() if utils.is_iter(value): for partval in value: varlist += (MSSP_VAR + bytes(variable, 'utf-8') + MSSP_VAL + bytes(partval, 'utf-8')) else: varlist += MSSP_VAR + bytes( variable, 'utf-8') + MSSP_VAL + bytes(value, 'utf-8') # send to crawler by subnegotiation self.protocol.requestNegotiation(MSSP, varlist) self.protocol.handshake_done()
def add(self, cmd): """ Add a command, a list of commands or a cmdset to this cmdset. Note that if cmd already exists in set, it will replace the old one (no priority checking etc at this point; this is often used to overload default commands). If cmd is another cmdset class or -instance, the commands of that command set is added to this one, as if they were part of the original cmdset definition. No merging or priority checks are made, rather later added commands will simply replace existing ones to make a unique set. """ if inherits_from(cmd, "evennia.commands.cmdset.CmdSet"): # cmd is a command set so merge all commands in that set # to this one. We raise a visible error if we created # an infinite loop (adding cmdset to itself somehow) try: cmd = self._instantiate(cmd) except RuntimeError: string = "Adding cmdset %(cmd)s to %(class)s lead to an " string += "infinite loop. When adding a cmdset to another, " string += "make sure they are not themself cyclically added to " string += "the new cmdset somewhere in the chain." raise RuntimeError(_(string) % {"cmd": cmd, "class": self.__class__}) cmds = cmd.commands elif is_iter(cmd): cmds = [self._instantiate(c) for c in cmd] else: cmds = [self._instantiate(cmd)] commands = self.commands system_commands = self.system_commands for cmd in cmds: # add all commands if not hasattr(cmd, 'obj'): cmd.obj = self.cmdsetobj try: ic = commands.index(cmd) commands[ic] = cmd # replace except ValueError: commands.append(cmd) # extra run to make sure to avoid doublets self.commands = list(set(commands)) #print "In cmdset.add(cmd):", self.key, cmd # add system_command to separate list as well, # for quick look-up if cmd.key.startswith("__"): try: ic = system_commands.index(cmd) system_commands[ic] = cmd # replace except ValueError: system_commands.append(cmd)
def session_from_sessid(self, sessid): """ Return session based on sessid, or None if not found """ if is_iter(sessid): return [ self.sessions.get(sid) for sid in sessid if sid in self.sessions ] return self.sessions.get(sessid)
def _iter(obj): typ = type(obj) tname = typ.__name__ if tname in ("_SaverDict", "dict"): return {_iter(key): _iter(val) for key, val in obj.items()} elif tname in _DESERIALIZE_MAPPING: return _DESERIALIZE_MAPPING[tname](_iter(val) for val in obj) elif is_iter(obj): return typ(_iter(val) for val in obj) return obj
def session_from_player(self, player, sessid): """ Given a player and a session id, return the actual session object """ if is_iter(sessid): sessions = [self.sessions.get(sid) for sid in sessid] s = [sess for sess in sessions if sess and sess.logged_in and player.uid == sess.uid] return s session = self.sessions.get(sessid) return session and session.logged_in and player.uid == session.uid and session or None
def page_formatter(self, page): """Input is a queryset page from django.Paginator""" caller = self._caller # get use-permissions of readonly attributes (edit is always False) display_tuples = [] table = EvTable( "|wKey|n", "|wSpawn/Edit|n", "|wTags|n", "|wDesc|n", border="tablecols", crop=True, width=self.width, ) for prototype in page: lock_use = caller.locks.check_lockstring(caller, prototype.get( "prototype_locks", ""), access_type="spawn", default=True) if not self.show_non_use and not lock_use: continue if prototype.get("prototype_key", "") in _MODULE_PROTOTYPES: lock_edit = False else: lock_edit = caller.locks.check_lockstring( caller, prototype.get("prototype_locks", ""), access_type="edit", default=True) if not self.show_non_edit and not lock_edit: continue ptags = [] for ptag in prototype.get("prototype_tags", []): if is_iter(ptag): if len(ptag) > 1: ptags.append("{}".format(ptag[0])) else: ptags.append(ptag[0]) else: ptags.append(str(ptag)) table.add_row( prototype.get("prototype_key", "<unset>"), "{}/{}".format("Y" if lock_use else "N", "Y" if lock_edit else "N"), ", ".join(list(set(ptags))), prototype.get("prototype_desc", "<unset>"), ) return str(table)
def sessions_from_puppet(self, puppet): """ Given a puppeted object, return all controlling sessions. """ sessid = puppet.sessid.get() if is_iter(sessid): return [ self.sessions.get(sid) for sid in sessid if sid in self.sessions ] return self.sessions.get(sessid)
def value_to_obj_or_any(value): "Convert value(s) to Object if possible, otherwise keep original value" stype = type(value) if is_iter(value): if stype == dict: return {value_to_obj_or_any(key): value_to_obj_or_any(val) for key, val in value.items()} else: return stype([value_to_obj_or_any(val) for val in value]) obj = dbid_to_obj(value, ObjectDB) return obj if obj is not None else value
def _visualize(obj, rootname, get_name=False): if is_iter(obj): if not obj: return str(obj) if get_name: return obj[0] if obj[0] else "<unset>" if rootname == "attrs": return "{} |w=|n {} |w(category:|n |n{}|w, locks:|n {}|w)|n".format(*obj) elif rootname == "tags": return "{} |w(category:|n {}|w)|n".format(obj[0], obj[1]) return "{}".format(obj)
def homogenize_prototype(prototype, custom_keys=None): """ Homogenize the more free-form prototype supported pre Evennia 0.7 into the stricter form. Args: prototype (dict): Prototype. custom_keys (list, optional): Custom keys which should not be interpreted as attrs, beyond the default reserved keys. Returns: homogenized (dict): Prototype where all non-identified keys grouped as attributes and other homogenizations like adding missing prototype_keys and setting a default typeclass. """ reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ()) attrs = list(prototype.get("attrs", [])) # break reference tags = make_iter(prototype.get("tags", [])) homogenized_tags = [] homogenized = {} for key, val in prototype.items(): if key in reserved: if key == "tags": for tag in tags: if not is_iter(tag): homogenized_tags.append((tag, None, None)) else: homogenized_tags.append(tag) else: homogenized[key] = val else: # unassigned keys -> attrs attrs.append((key, val, None, "")) if attrs: homogenized["attrs"] = attrs if homogenized_tags: homogenized["tags"] = homogenized_tags # add required missing parts that had defaults before if "prototype_key" not in prototype: # assign a random hash as key homogenized["prototype_key"] = "prototype-{}".format( hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:7] ) if "typeclass" not in prototype and "prototype_parent" not in prototype: homogenized["typeclass"] = settings.BASE_OBJECT_TYPECLASS return homogenized
def decode_gmcp(self, data): """ Decodes incoming GMCP data on the form 'varname <structure>'. Args: data (str or list): GMCP data. Notes: Clients send data on the form "Module.Submodule.Cmdname <structure>". We assume the structure is valid JSON. The following is parsed into Evennia's formal structure: ``` Core.Name -> [name, [], {}] Core.Name string -> [name, [string], {}] Core.Name [arg, arg,...] -> [name, [args], {}] Core.Name {key:arg, key:arg, ...} -> [name, [], {kwargs}] Core.Name [[args], {kwargs}] -> [name, [args], {kwargs}] ``` """ if isinstance(data, list): data = b"".join(data) # print("decode_gmcp in:", data) # DEBUG if data: try: cmdname, structure = data.split(None, 1) except ValueError: cmdname, structure = data, b"" cmdname = cmdname.replace(b".", b"_") try: structure = json.loads(structure) except ValueError: # maybe the structure is not json-serialized at all pass args, kwargs = [], {} if is_iter(structure): if isinstance(structure, dict): kwargs = { key: value for key, value in structure.items() if key } else: args = list(structure) else: args = (structure, ) if cmdname.lower().startswith(b"core_"): # if Core.cmdname, then use cmdname cmdname = cmdname[5:] self.protocol.data_in(**{cmdname.lower().decode(): [args, kwargs]})
def _to_ansi(obj, regexable=False): "convert to ANSIString" if isinstance(obj, str): # since ansi will be parsed twice (here and in the normal ansi send), we have to # escape the |-structure twice. obj = _ANSI_ESCAPE.sub(r"||||", obj) if isinstance(obj, dict): return dict((key, _to_ansi(value, regexable=regexable)) for key, value in obj.items()) elif is_iter(obj): return [_to_ansi(o) for o in obj] else: return ANSIString(obj, regexable=regexable)
def session_from_player(self, player, sessid): """ Given a player and a session id, return the actual session object """ if is_iter(sessid): sessions = [self.sessions.get(sid) for sid in sessid] s = [ sess for sess in sessions if sess and sess.logged_in and player.uid == sess.uid ] return s session = self.sessions.get(sessid) return session and session.logged_in and player.uid == session.uid and session or None
def homogenize_prototype(prototype, custom_keys=None): """ Homogenize the more free-form prototype supported pre Evennia 0.7 into the stricter form. Args: prototype (dict): Prototype. custom_keys (list, optional): Custom keys which should not be interpreted as attrs, beyond the default reserved keys. Returns: homogenized (dict): Prototype where all non-identified keys grouped as attributes and other homogenizations like adding missing prototype_keys and setting a default typeclass. """ reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ()) attrs = list(prototype.get('attrs', [])) # break reference tags = make_iter(prototype.get('tags', [])) homogenized_tags = [] homogenized = {} for key, val in prototype.items(): if key in reserved: if key == 'tags': for tag in tags: if not is_iter(tag): homogenized_tags.append((tag, None, None)) else: homogenized_tags.append(tag) else: homogenized[key] = val else: # unassigned keys -> attrs attrs.append((key, val, None, '')) if attrs: homogenized['attrs'] = attrs if homogenized_tags: homogenized['tags'] = homogenized_tags # add required missing parts that had defaults before if "prototype_key" not in prototype: # assign a random hash as key homogenized["prototype_key"] = "prototype-{}".format( hashlib.md5(str(time.time())).hexdigest()[:7]) if "typeclass" not in prototype and "prototype_parent" not in prototype: homogenized["typeclass"] = settings.BASE_OBJECT_TYPECLASS return homogenized
def session_from_sessid(self, sessid): """ Get session based on sessid, or None if not found Args: sessid (int or list): Session id(s) Return: sessions (Session or list): Session(s) found. """ if is_iter(sessid): return [self.sessions.get(sid) for sid in sessid if sid in self.sessions] return self.sessions.get(sessid)
def session_from_sessid(self, sessid): """ Get session based on sessid, or None if not found Args: sessid (int or list): Session id(s). Return: sessions (Session or list): Session(s) found. This is a list if input was a list. """ if is_iter(sessid): return [self.get(sid) for sid in sessid if sid in self] return self.get(sessid)
def sessions_from_puppet(self, puppet): """ Given a puppeted object, return all controlling sessions. Args: puppet (Object): Object puppeted Returns. sessions (Session or list): Can be more than one of Object is controlled by more than one Session (MULTISESSION_MODE > 1). """ sessid = puppet.sessid.get() if is_iter(sessid): return [self.sessions.get(sid) for sid in sessid if sid in self.sessions] return self.sessions.get(sessid)
def _recursive_diff(old, new, depth=0): old_type = type(old) new_type = type(new) if old_type == new_type and not (old or new): # both old and new are unset, like [] or None return (None, None, "KEEP") if old_type != new_type: if old and not new: if depth < maxdepth and old_type == dict: return {key: (part, None, "REMOVE") for key, part in old.items()} elif depth < maxdepth and is_iter(old): return { part[0] if is_iter(part) else part: (part, None, "REMOVE") for part in old } if isinstance(new, Unset) and implicit_keep: # the new does not define any change, use implicit-keep return (old, None, "KEEP") return (old, new, "REMOVE") elif not old and new: if depth < maxdepth and new_type == dict: return {key: (None, part, "ADD") for key, part in new.items()} elif depth < maxdepth and is_iter(new): return {part[0] if is_iter(part) else part: (None, part, "ADD") for part in new} return (old, new, "ADD") else: # this condition should not occur in a standard diff return (old, new, "UPDATE") elif depth < maxdepth and new_type == dict: all_keys = set(list(old.keys()) + list(new.keys())) return { key: _recursive_diff(old.get(key, _unset), new.get(key, _unset), depth=depth + 1) for key in all_keys } elif depth < maxdepth and is_iter(new): old_map = {part[0] if is_iter(part) else part: part for part in old} new_map = {part[0] if is_iter(part) else part: part for part in new} all_keys = set(list(old_map.keys()) + list(new_map.keys())) return { key: _recursive_diff( old_map.get(key, _unset), new_map.get(key, _unset), depth=depth + 1 ) for key in all_keys } elif old != new: return (old, new, "UPDATE") else: return (old, new, "KEEP")
def get_objs_with_key_or_alias(self, ostring, exact=True, candidates=None, typeclasses=None): """ Returns objects based on key or alias match. Will also do fuzzy matching based on the `utils.string_partial_matching` function. candidates - list of candidate objects to restrict on typeclasses - list of typeclass path strings to restrict on """ if not isinstance(ostring, basestring): if hasattr(ostring, "key"): ostring = ostring.key else: return [] if is_iter(candidates) and not len(candidates): # if candidates is an empty iterable there can be no matches # Exit early. return [] # build query objects candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj] cand_restriction = candidates != None and Q(pk__in=make_iter(candidates_id)) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() if exact: # exact match - do direct search return self.filter(cand_restriction & type_restriction & (Q(db_key__iexact=ostring) | Q(db_tags__db_key__iexact=ostring) & Q(db_tags__db_tagtype__iexact="alias"))).distinct() elif candidates: # fuzzy with candidates key_candidates = self.filter(cand_restriction & type_restriction) else: # fuzzy without supplied candidates - we select our own candidates key_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) | Q(db_tags__db_key__istartswith=ostring))).distinct() candidates_id = [_GA(obj, "id") for obj in key_candidates] # fuzzy matching key_strings = key_candidates.values_list("db_key", flat=True).order_by("id") index_matches = string_partial_matching(key_strings, ostring, ret_index=True) if index_matches: return [obj for ind, obj in enumerate(key_candidates) if ind in index_matches] else: alias_candidates = self.filter(id__in=candidates_id, db_tags__db_tagtype__iexact="alias") alias_strings = alias_candidates.values_list("db_key", flat=True) index_matches = string_partial_matching(alias_strings, ostring, ret_index=True) if index_matches: return [alias.db_obj for ind, alias in enumerate(alias_candidates) if ind in index_matches] return []
def session_from_sessid(self, sessid): """ Get session based on sessid, or None if not found Args: sessid (int or list): Session id(s) Return: sessions (Session or list): Session(s) found. """ if is_iter(sessid): return [ self.sessions.get(sid) for sid in sessid if sid in self.sessions ] return self.sessions.get(sessid)
def sessions_from_puppet(self, puppet): """ Given a puppeted object, return all controlling sessions. Args: puppet (Object): Object puppeted Returns. sessions (Session or list): Can be more than one of Object is controlled by more than one Session (MULTISESSION_MODE > 1). """ sessid = puppet.sessid.get() if is_iter(sessid): return [ self.sessions.get(sid) for sid in sessid if sid in self.sessions ] return self.sessions.get(sessid)
def session_from_player(self, player, sessid): """ Given a player and a session id, return the actual session object. Args: player (Player): The Player to get the Session from. sessid (int or list): Session id(s). Returns: sessions (Session or list): Session(s) found. """ if is_iter(sessid): sessions = [self.sessions.get(sid) for sid in sessid] s = [sess for sess in sessions if sess and sess.logged_in and player.uid == sess.uid] return s session = self.sessions.get(sessid) return session and session.logged_in and player.uid == session.uid and session or None
def set_aliases(self, new_aliases): """ Update aliases. Args: new_aliases (list): Notes: This is necessary to use to make sure the optimization caches are properly updated as well. """ if not is_iter(new_aliases): try: self.aliases = [str(alias).strip().lower() for alias in self.aliases.split(",")] except Exception: self.aliases = [] self.aliases = list(set(alias for alias in self.aliases if alias and alias != self.key)) self._optimize()
def at_look(self, target=None, session=None): """ Called when this object executes a look. It allows to customize just what this means. Args: target (Object or list, optional): An object or a list objects to inspect. session (Session, optional): The session doing this look. Returns: look_string (str): A prepared look string, ready to send off to any recipient (usually to ourselves) """ if target and not is_iter(target): # single target - just show it return target.return_appearance() else: self.render.render_login(session)
def set_aliases(self, new_aliases): """ Update aliases. Args: new_aliases (list): Notes: This is necessary to use to make sure the optimization caches are properly updated as well. """ if not is_iter(new_aliases): try: self.aliases = [str(alias).strip().lower() for alias in self.aliases.split(',')] except Exception: self.aliases = [] self.aliases = list(set(alias for alias in self.aliases if alias and alias != self.key)) self._optimize()
def search_dbref(self, dbref, location=None): """ Search as an object by its dbref. Args: dbref: (string)dbref. Returns: The object or None. """ match = ObjectDB.objects.dbref_search(dbref) if match and location: # match the location if is_iter(location): if not [l for l in location if match.location == l]: match = None elif match.location != location: match = None return match
def session_from_player(self, player, sessid): """ Given a player and a session id, return the actual session object. Args: player (Player): The Player to get the Session from. sessid (int or list): Session id(s). Returns: sessions (Session or list): Session(s) found. """ if is_iter(sessid): sessions = [self.sessions.get(sid) for sid in sessid] s = [ sess for sess in sessions if sess and sess.logged_in and player.uid == sess.uid ] return s session = self.sessions.get(sessid) return session and session.logged_in and player.uid == session.uid and session or None
def _recursive_diff(old, new, depth=0): old_type = type(old) new_type = type(new) if old_type != new_type: if old and not new: if depth < maxdepth and old_type == dict: return {key: (part, None, "REMOVE") for key, part in old.items()} elif depth < maxdepth and is_iter(old): return {part[0] if is_iter(part) else part: (part, None, "REMOVE") for part in old} return (old, new, "REMOVE") elif not old and new: if depth < maxdepth and new_type == dict: return {key: (None, part, "ADD") for key, part in new.items()} elif depth < maxdepth and is_iter(new): return {part[0] if is_iter(part) else part: (None, part, "ADD") for part in new} return (old, new, "ADD") else: # this condition should not occur in a standard diff return (old, new, "UPDATE") elif depth < maxdepth and new_type == dict: all_keys = set(old.keys() + new.keys()) return {key: _recursive_diff(old.get(key), new.get(key), depth=depth + 1) for key in all_keys} elif depth < maxdepth and is_iter(new): old_map = {part[0] if is_iter(part) else part: part for part in old} new_map = {part[0] if is_iter(part) else part: part for part in new} all_keys = set(old_map.keys() + new_map.keys()) return {key: _recursive_diff(old_map.get(key), new_map.get(key), depth=depth + 1) for key in all_keys} elif old != new: return (old, new, "UPDATE") else: return (old, new, "KEEP")
def at_look(self, target=None, session=None): """ Called when this object executes a look. It allows to customize just what this means. Args: target (Object or list, optional): An object or a list objects to inspect. session (Session, optional): The session doing this look. Returns: look_string (str): A prepared look string, ready to send off to any recipient (usually to ourselves) """ if target and not is_iter(target): # single target - just show it return target.return_appearance() else: # list of targets - make list characters = target sessions = self.sessions.all() is_su = self.is_superuser # text shown when looking in the ooc area string = "Account {g%s{n (you are Out-of-Character)" % (self.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" % (session.sessid == csessid and "{w* %s{n" % (isess + 1) or " %s" % (isess + 1), addr) string += "\n\n {whelp{n - more commands" string += "\n {wooc <Text>{n - talk on public channel" charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1 if is_su or len(characters) < charmax: if not characters: string += "\n\n You don't have any characters yet. See {whelp @charcreate{n for creating one." else: string += "\n {w@charcreate <name> [=description]{n - create new character" if characters: string_s_ending = len(characters) > 1 and "s" or "" string += "\n {w@ic <character>{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, charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "") for char in characters: csessions = char.sessions.all() if csessions: for sess in csessions: # character is already puppeted 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) return string
def batch_add(self, *args, **kwargs): """ Batch-version of `add()`. This is more efficient than repeat-calling add when having many Attributes to add. Args: indata (tuple): Tuples of varying length representing the Attribute to add to this object. - `(key, value)` - `(key, value, category)` - `(key, value, category, lockstring)` - `(key, value, category, lockstring, default_access)` Kwargs: strattr (bool): If `True`, value must be a string. This will save the value without pickling which is less flexible but faster to search (not often used except internally). Raises: RuntimeError: If trying to pass a non-iterable as argument. Notes: The indata tuple order matters, so if you want a lockstring but no category, set the category to `None`. This method does not have the ability to check editing permissions like normal .add does, and is mainly used internally. It does not use the normal self.add but apply the Attributes directly to the database. """ new_attrobjs = [] strattr = kwargs.get('strattr', False) for tup in args: if not is_iter(tup) or len(tup) < 2: raise RuntimeError("batch_add requires iterables as arguments (got %r)." % tup) ntup = len(tup) keystr = str(tup[0]).strip().lower() new_value = tup[1] category = str(tup[2]).strip().lower() if ntup > 2 else None lockstring = tup[3] if ntup > 3 else "" attr_objs = self._getcache(keystr, category) if attr_objs: attr_obj = attr_objs[0] # update an existing attribute object attr_obj.db_category = category attr_obj.db_lock_storage = lockstring attr_obj.save(update_fields=["db_category", "db_lock_storage"]) if strattr: # store as a simple string (will not notify OOB handlers) attr_obj.db_strvalue = new_value attr_obj.save(update_fields=["db_strvalue"]) else: # store normally (this will also notify OOB handlers) attr_obj.value = new_value else: # create a new Attribute (no OOB handlers can be notified) kwargs = {"db_key": keystr, "db_category": category, "db_model": self._model, "db_attrtype": self._attrtype, "db_value": None if strattr else to_pickle(new_value), "db_strvalue": new_value if strattr else None, "db_lock_storage": lockstring} new_attr = Attribute(**kwargs) new_attr.save() new_attrobjs.append(new_attr) self._setcache(keystr, category, new_attr) if new_attrobjs: # Add new objects to m2m field all at once getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs)
def swap_typeclass(self, new_typeclass, clean_attributes=False, run_start_hooks=True, no_default=True): """ This performs an in-situ swap of the typeclass. This means that in-game, this object will suddenly be something else. Player will not be affected. To 'move' a player to a different object entirely (while retaining this object's type), use self.player.swap_object(). Note that this might be an error prone operation if the old/new typeclass was heavily customized - your code might expect one and not the other, so be careful to bug test your code if using this feature! Often its easiest to create a new object and just swap the player over to that one instead. Args: new_typeclass (str or classobj): Type to switch to. clean_attributes (bool or list, optional): Will delete all attributes stored on this object (but not any of the database fields such as name or location). You can't get attributes back, but this is often the safest bet to make sure nothing in the new typeclass clashes with the old one. If you supply a list, only those named attributes will be cleared. run_start_hooks (bool, optional): Trigger the start hooks of the object, as if it was created for the first time. no_default (bool, optiona): If set, the swapper will not allow for swapping to a default typeclass in case the given one fails for some reason. Instead the old one will be preserved. Returns: result (bool): True/False depending on if the swap worked or not. """ if not callable(new_typeclass): # this is an actual class object - build the path new_typeclass = class_from_module(new_typeclass, defaultpaths=settings.TYPECLASS_PATHS) # if we get to this point, the class is ok. if inherits_from(self, "evennia.scripts.models.ScriptDB"): if self.interval > 0: raise RuntimeError("Cannot use swap_typeclass on time-dependent " \ "Script '%s'.\nStop and start a new Script of the " \ "right type instead." % self.key) self.typeclass_path = new_typeclass.path self.__class__ = new_typeclass if clean_attributes: # Clean out old attributes if is_iter(clean_attributes): for attr in clean_attributes: self.attributes.remove(attr) for nattr in clean_attributes: if hasattr(self.ndb, nattr): self.nattributes.remove(nattr) else: self.attributes.clear() self.nattributes.clear() if run_start_hooks: # fake this call to mimic the first save self.at_first_save()
def audit(self, **kwargs): """ Extracts messages and system data from a Session object upon message send or receive. Kwargs: src (str): Source of data; 'client' or 'server'. Indicates direction. text (str or list): Client sends messages to server in the form of lists. Server sends messages to client as string. Returns: log (dict): Dictionary object containing parsed system and user data related to this message. """ # Get time at start of processing time_obj = timezone.now() time_str = str(time_obj) session = self src = kwargs.pop('src', '?') bytecount = 0 # Do not log empty lines if not kwargs: return {} # Get current session's IP address client_ip = session.address # Capture Account name and dbref together account = session.get_account() account_token = '' if account: account_token = '%s%s' % (account.key, account.dbref) # Capture Character name and dbref together char = session.get_puppet() char_token = '' if char: char_token = '%s%s' % (char.key, char.dbref) # Capture Room name and dbref together room = None room_token = '' if char: room = char.location room_token = '%s%s' % (room.key, room.dbref) # Try to compile an input/output string def drill(obj, bucket): if isinstance(obj, dict): return bucket elif utils.is_iter(obj): for sub_obj in obj: bucket.extend(drill(sub_obj, [])) else: bucket.append(obj) return bucket text = kwargs.pop('text', '') if utils.is_iter(text): text = '|'.join(drill(text, [])) # Mask any PII in message, where possible bytecount = len(text.encode('utf-8')) text = self.mask(text) # Compile the IP, Account, Character, Room, and the message. log = { 'time': time_str, 'hostname': socket.getfqdn(), 'application': '%s' % ev_settings.SERVERNAME, 'version': get_evennia_version(), 'pid': os.getpid(), 'direction': 'SND' if src == 'server' else 'RCV', 'protocol': self.protocol_key, 'ip': client_ip, 'session': 'session#%s' % self.sessid, 'account': account_token, 'character': char_token, 'room': room_token, 'text': text.strip(), 'bytes': bytecount, 'data': kwargs, 'objects': { 'time': time_obj, 'session': self, 'account': account, 'character': char, 'room': room, } } # Remove any keys with blank values if AUDIT_ALLOW_SPARSE is False: log['data'] = {k: v for k, v in log['data'].iteritems() if v} log['objects'] = {k: v for k, v in log['objects'].iteritems() if v} log = {k: v for k, v in log.iteritems() if v} return log
def do_mssp(self, option): """ Negotiate all the information. Args: option (Option): Not used. """ self.mssp_table = { # Required fields "NAME": "Evennia", "PLAYERS": self.get_player_count, "UPTIME" : self.get_uptime, # Generic "CRAWL DELAY": "-1", "HOSTNAME": "", # current or new hostname "PORT": ["4000"], # most important port should be last in list "CODEBASE": "Evennia", "CONTACT": "", # email for contacting the mud "CREATED": "", # year MUD was created "ICON": "", # url to icon 32x32 or larger; <32kb. "IP": "", # current or new IP address "LANGUAGE": "", # name of language used, e.g. English "LOCATION": "", # full English name of server country "MINIMUM AGE": "0", # set to 0 if not applicable "WEBSITE": "www.evennia.com", # Categorisation "FAMILY": "Custom", # evennia goes under 'Custom' "GENRE": "None", # Adult, Fantasy, Historical, Horror, Modern, None, or Science Fiction "GAMEPLAY": "None", # Adventure, Educational, Hack and Slash, None, # Player versus Player, Player versus Environment, # Roleplaying, Simulation, Social or Strategy "STATUS": "Open Beta", # Alpha, Closed Beta, Open Beta, Live "GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew "INTERMUD": "IMC2", # evennia supports IMC2. "SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein, # Cyberpunk, Dragonlance, etc. Or None if not available. # World "AREAS": "0", "HELPFILES": "0", "MOBILES": "0", "OBJECTS": "0", "ROOMS": "0", # use 0 if room-less "CLASSES": "0", # use 0 if class-less "LEVELS": "0", # use 0 if level-less "RACES": "0", # use 0 if race-less "SKILLS": "0", # use 0 if skill-less # Protocols set to 1 or 0) "ANSI": "1", "GMCP": "0", "MCCP": "0", "MCP": "0", "MSDP": "0", "MSP": "0", "MXP": "0", "PUEBLO": "0", "UTF-8": "1", "VT100": "0", "XTERM 256 COLORS": "0", # Commercial set to 1 or 0) "PAY TO PLAY": "0", "PAY FOR PERKS": "0", # Hiring set to 1 or 0) "HIRING BUILDERS": "0", "HIRING CODERS": "0", # Extended variables # World "DBSIZE": "0", "EXITS": "0", "EXTRA DESCRIPTIONS": "0", "MUDPROGS": "0", "MUDTRIGS": "0", "RESETS": "0", # Game (set to 1, 0 or one of the given alternatives) "ADULT MATERIAL": "0", "MULTICLASSING": "0", "NEWBIE FRIENDLY": "0", "PLAYER CITIES": "0", "PLAYER CLANS": "0", "PLAYER CRAFTING": "0", "PLAYER GUILDS": "0", "EQUIPMENT SYSTEM": "None", # "None", "Level", "Skill", "Both" "MULTIPLAYING": "None", # "None", "Restricted", "Full" "PLAYERKILLING": "None", # "None", "Restricted", "Full" "QUEST SYSTEM": "None", # "None", "Immortal Run", "Automated", "Integrated" "ROLEPLAYING": "None", # "None", "Accepted", "Encouraged", "Enforced" "TRAINING SYSTEM": "None", # "None", "Level", "Skill", "Both" "WORLD ORIGINALITY": "None", # "All Stock", "Mostly Stock", "Mostly Original", "All Original" # Protocols (only change if you added/removed something manually) "ATCP": "0", "MSDP": "0", "MCCP": "1", "SSL": "1", "UTF-8": "1", "ZMP": "0", "XTERM 256 COLORS": "0"} # update the static table with the custom one if MSSPTable_CUSTOM: self.mssp_table.update(MSSPTable_CUSTOM) varlist = '' for variable, value in self.mssp_table.items(): if callable(value): value = value() if utils.is_iter(value): for partval in value: varlist += MSSP_VAR + str(variable) + MSSP_VAL + str(partval) else: varlist += MSSP_VAR + str(variable) + MSSP_VAL + str(value) # send to crawler by subnegotiation self.protocol.requestNegotiation(MSSP, varlist) self.protocol.handshake_done()
def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_edit=True): """ Collate a list of found prototypes based on search criteria and access. Args: caller (Account or Object): The object requesting the list. key (str, optional): Exact or partial prototype key to query for. tags (str or list, optional): Tag key or keys to query for. show_non_use (bool, optional): Show also prototypes the caller may not use. show_non_edit (bool, optional): Show also prototypes the caller may not edit. Returns: table (EvTable or None): An EvTable representation of the prototypes. None if no prototypes were found. """ # this allows us to pass lists of empty strings tags = [tag for tag in make_iter(tags) if tag] # get prototypes for readonly and db-based prototypes prototypes = search_prototype(key, tags) # get use-permissions of readonly attributes (edit is always False) display_tuples = [] for prototype in sorted(prototypes, key=lambda d: d.get('prototype_key', '')): lock_use = caller.locks.check_lockstring( caller, prototype.get('prototype_locks', ''), access_type='spawn', default=True) if not show_non_use and not lock_use: continue if prototype.get('prototype_key', '') in _MODULE_PROTOTYPES: lock_edit = False else: lock_edit = caller.locks.check_lockstring( caller, prototype.get('prototype_locks', ''), access_type='edit', default=True) if not show_non_edit and not lock_edit: continue ptags = [] for ptag in prototype.get('prototype_tags', []): if is_iter(ptag): if len(ptag) > 1: ptags.append("{} (category: {}".format(ptag[0], ptag[1])) else: ptags.append(ptag[0]) else: ptags.append(str(ptag)) display_tuples.append( (prototype.get('prototype_key', '<unset>'), prototype.get('prototype_desc', '<unset>'), "{}/{}".format('Y' if lock_use else 'N', 'Y' if lock_edit else 'N'), ",".join(ptags))) if not display_tuples: return "" table = [] width = 78 for i in range(len(display_tuples[0])): table.append([str(display_tuple[i]) for display_tuple in display_tuples]) table = EvTable("Key", "Desc", "Spawn/Edit", "Tags", table=table, crop=True, width=width) table.reformat_column(0, width=22) table.reformat_column(1, width=29) table.reformat_column(2, width=11, align='c') table.reformat_column(3, width=16) return table
def _to_batchtuple(inp, *args): "build tuple suitable for batch-creation" if is_iter(inp): # already a tuple/list, use as-is return inp return (inp, ) + args
def batch_add(self, *args, **kwargs): """ Batch-version of `add()`. This is more efficient than repeat-calling add when having many Attributes to add. Args: indata (list): List of tuples of varying length representing the Attribute to add to this object. Supported tuples are - `(key, value)` - `(key, value, category)` - `(key, value, category, lockstring)` - `(key, value, category, lockstring, default_access)` Kwargs: strattr (bool): If `True`, value must be a string. This will save the value without pickling which is less flexible but faster to search (not often used except internally). Raises: RuntimeError: If trying to pass a non-iterable as argument. Notes: The indata tuple order matters, so if you want a lockstring but no category, set the category to `None`. This method does not have the ability to check editing permissions like normal .add does, and is mainly used internally. It does not use the normal self.add but apply the Attributes directly to the database. """ new_attrobjs = [] strattr = kwargs.get('strattr', False) for tup in args: if not is_iter(tup) or len(tup) < 2: raise RuntimeError( "batch_add requires iterables as arguments (got %r)." % tup) ntup = len(tup) keystr = str(tup[0]).strip().lower() new_value = tup[1] category = str(tup[2]).strip().lower( ) if ntup > 2 and tup[2] is not None else None lockstring = tup[3] if ntup > 3 else "" attr_objs = self._getcache(keystr, category) if attr_objs: attr_obj = attr_objs[0] # update an existing attribute object attr_obj.db_category = category attr_obj.db_lock_storage = lockstring or '' attr_obj.save(update_fields=["db_category", "db_lock_storage"]) if strattr: # store as a simple string (will not notify OOB handlers) attr_obj.db_strvalue = new_value attr_obj.save(update_fields=["db_strvalue"]) else: # store normally (this will also notify OOB handlers) attr_obj.value = new_value else: # create a new Attribute (no OOB handlers can be notified) kwargs = { "db_key": keystr, "db_category": category, "db_model": self._model, "db_attrtype": self._attrtype, "db_value": None if strattr else to_pickle(new_value), "db_strvalue": new_value if strattr else None, "db_lock_storage": lockstring or '' } new_attr = Attribute(**kwargs) new_attr.save() new_attrobjs.append(new_attr) self._setcache(keystr, category, new_attr) if new_attrobjs: # Add new objects to m2m field all at once getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs)
def at_look(self, target=None, session=None, **kwargs): """ Called when this object executes a look. It allows to customize just what this means. Args: target (Object or list, optional): An object or a list objects to inspect. session (Session, optional): The session doing this look. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). Returns: look_string (str): A prepared look string, ready to send off to any recipient (usually to ourselves) """ if target and not is_iter(target): # single target - just show it if hasattr(target, "return_appearance"): return target.return_appearance(self) else: return "{} has no in-game appearance.".format(target) else: # list of targets - make list to disconnect from db characters = list(tar for tar in target if tar) if target else [] sessions = self.sessions.all() is_su = self.is_superuser # text shown when looking in the ooc area result = ["Account |g%s|n (you are Out-of-Character)" % self.key] nsess = len(sessions) result.append(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)) result.append("\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1) or " %s" % (isess + 1), addr)) result.append("\n\n |whelp|n - more commands") result.append("\n |wooc <Text>|n - talk on public channel") charmax = _MAX_NR_CHARACTERS if is_su or len(characters) < charmax: if not characters: result.append("\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one.") else: result.append("\n |w@charcreate <name> [=description]|n - create new character") result.append("\n |w@chardelete <name>|n - delete a character (cannot be undone!)") if characters: string_s_ending = len(characters) > 1 and "s" or "" result.append("\n |w@ic <character>|n - enter the game (|w@ooc|n to get back here)") if is_su: result.append("\n\nAvailable character%s (%i/unlimited):" % (string_s_ending, len(characters))) else: result.append("\n\nAvailable character%s%s:" % (string_s_ending, charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "")) for char in characters: csessions = char.sessions.all() if csessions: for sess in csessions: # character is already puppeted sid = sess in sessions and sessions.index(sess) + 1 if sess and sid: result.append("\n - |G%s|n [%s] (played by you in session %i)" % (char.key, ", ".join(char.permissions.all()), sid)) else: result.append("\n - |R%s|n [%s] (played by someone else)" % (char.key, ", ".join(char.permissions.all()))) else: # character is "free to puppet" result.append("\n - %s [%s]" % (char.key, ", ".join(char.permissions.all()))) look_string = ("-" * 68) + "\n" + "".join(result) + "\n" + ("-" * 68) return look_string