From 893cffc3e153083961dc0f6ef1b5bc404f2f00eb Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 25 Jun 2016 12:40:43 +0200 Subject: [PATCH] Add multidescer contrib, inspired by MUSH origins. --- evennia/contrib/multidescer.py | 237 +++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 evennia/contrib/multidescer.py diff --git a/evennia/contrib/multidescer.py b/evennia/contrib/multidescer.py new file mode 100644 index 0000000000..a19d700ca8 --- /dev/null +++ b/evennia/contrib/multidescer.py @@ -0,0 +1,237 @@ +""" +Evennia Mutltidescer + +Contrib - Griatch 2016 + +A "multidescer" is a concept from the MUSH world. It allows for +creating, managing and switching between multiple character +descriptions. This multidescer will not require any changes to the +Character class, rather it will use the `multidescs` Attribute (a +list) and create it if it does not exist. + +This contrib also works well together with the rpsystem contrib (which +also adds the short descriptions and the `sdesc` command). + + +Installation: + +Edit `mygame/commands/default_cmdsets.py` and add +`from contrib.multidesc import CmdMultiDesc` to the top. + +Next, look up the `at_cmdset_create` method of the `CharacterCmdSet` +class and add a line `self.add(CmdMultiDesc())` to the end +of it. + +Reload the server and you should have the +desc command available (it +will replace the default `desc` command). + +""" +from evennia import default_cmds +from evennia.utils.utils import crop +from evennia.utils.eveditor import EvEditor + + +# Helper functions for the Command + + +def _update_store(caller, num=0, text=None, replace=False): + """ + Helper function for updating the database store. + + Args: + caller (Object): The caller of the command. + num (int): Index of store to update. + text (str): Description text. + replace (bool): Replace or amend description. + + """ + if not caller.db.multidesc: + # initialize the multidesc attribute + caller.db.multidesc = [caller.db.desc or ""] + if not text: + return + if replace: + caller.db.multidesc[num] = text + else: + caller.db.multidesc.insert(num, text) + + +class NumValidateError(ValueError): + "Used for tracebacks from _validate_num" + pass + + +def _validate_num(caller, num): + """ + Check so the given num is a valid number in the storage interval. + + Args: + caller (Object): The caller of the command + num (str): The number to validate. + + Returns: + num (int): Returns the valid index (starting from 0) + + Raises: + NumValidateError: For malformed numbers. + + Notes: + This function will also report errors. + + """ + if not caller.db.multidesc: + _update_store(caller) + nlen = len(caller.db.multidesc) + if not num.strip().isdigit() or not (0 < int(num) <= nlen): + raise NumValidateError("%s must be a number between 1 and %i." % + ('%s' % num or "Argument", nlen)) + else: + return int(num) - 1 + + +# eveditor save/load/quit functions + +def _save_editor(caller, buffer): + "Called when the editor saves its contents" + num = caller.db._multidesc_editnum + replace = caller.db._multidesc_editreplace + if num is not None: + _update_store(caller, num, buffer, replace=replace) + caller.msg("Saved the buffer to description slot %i." % (num + 1)) + return True + +def _load_editor(caller): + "Called when the editor loads contents" + num = caller.db._multidesc_editnum + if num is not None: + try: + return caller.db.multidesc[num] + except IndexError: + pass + return "" + +def _quit_editor(caller): + "Called when the editor quits" + del caller.db._multidesc_editnum + del caller.db._multidesc_editreplace + caller.msg("Exited editor.") + + +# The actual command class + +class CmdMultiDesc(default_cmds.MuxCommand): + """ + Manage multiple descriptions + + Usage: + +desc [n] - show current or desc + +desc/list - list descriptions (abbreviated) + +desc/list/full - list descriptions (full texts) + +desc/add [ =] - add new desc or replace desc + +desc/edit [n] - open editor to modify current or desc + +desc/del [n] - delete current or desc + +desc/swap - - reorder list by swapping #n1 and + +desc/set - set which desc as active desc + + """ + key = "+desc" + aliases = ["desc"] + locks = "cmd:all()" + help_category = "General" + + def func(self): + """ + Implements the multidescer. We will use `db.desc` for the + description in use and `db.multidesc` to store all descriptions. + """ + + caller = self.caller + args = self.args.strip() + switches = self.switches + + try: + if "list" in switches or "all" in switches: + # list all stored descriptions, either in full or cropped. + # Note that we list starting from 1, not from 0. + _update_store(caller) + do_crop = not "full" in switches + outtext = "|wStored descs:|n" + for inum, desc in enumerate(caller.db.multidesc): + outtext += "\n|w%i:|n %s" % (inum + 1, crop(desc) if do_crop else "\n%s" % desc) + caller.msg(outtext) + + elif "add" in switches: + # add text directly to a new entry or an existing one. + if self.rhs: + # this means a '=' was given + num, desc = _validate_num(caller, self.lhs), self.rhs + replace = True + else: + num, desc = 0, self.args + replace = False + if not desc: + caller.msg("No description given.") + return + _update_store(caller, num, desc, replace=replace) + caller.msg("Stored description in slot %i: \"%s\"" % (num + 1, crop(desc))) + + elif "edit" in switches: + # Use the eveditor to edit the description. + if args: + num = _validate_num(caller, args) + replace = True + else: + num = 1 + replace = False + # this is used by the editor to know what to edit; it's deleted automatically + caller.db._multidesc_editnum = num + caller.db._multidesc_editreplace = replace + # start the editor + EvEditor(caller, loadfunc=_load_editor, savefunc=_save_editor, + quitfunc=_quit_editor, key="multidesc editor", persistent=True) + + elif "delete" in switches or "del" in switches: + # delete a multidesc entry. + num = _validate_num(caller, args) + del caller.db.multidesc[num] + caller.msg("Deleted description number %i." % (num + 1)) + + elif "swap" in switches or "switch" in switches or "reorder" in switches: + # Reorder list by swapping two entries. We expect numbers starting from 1 + nums = [_validate_num(caller, arg) for arg in args.split("-", 1)] + if not len(nums) == 2: + caller.msg("To swap two desc entries, use |w%s/swap - |n" % (self.key)) + return + num1, num2 = nums + if num1 == num2: + caller.msg("Swapping position with itself changes nothing.") + return + # perform the swap + desc1, desc2 = caller.db.multidesc[num1], caller.db.multidesc[num2] + caller.db.multidesc[num2] = desc1 + caller.db.multidesc[num1] = desc2 + caller.msg("Swapped descs numbers %i and %i." % (num1 + 1, num2 + 1)) + + elif "set" in switches: + # switches one of the multidescs to be the "active", + # description, with numbers starting from 1 + if args: + num = _validate_num(caller, args) + else: + _update_store(caller) + num = 0 + # activating this description + caller.db.desc = caller.db.multidesc[num] + caller.msg("|wSet description %i as the current one:|n\n%s" % (num + 1, crop(caller.db.desc))) + + else: + # display the current description or a numbered description + if args: + num = _validate_num(caller, args) + caller.msg("|wDecsription number %i:|n\n%s" % (num + 1, caller.db.multidesc[num])) + else: + caller.msg("|wCurrent desc:|n\n%s" % caller.db.desc) + + except NumValidateError, err: + # This is triggered by _validate_num + caller.msg(err)