From fca1edbb3842a65c0ae524cf2980f2c0017e153f Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Wed, 30 May 2018 22:59:45 -0700 Subject: [PATCH 01/17] Add fieldfill.py --- evennia/contrib/fieldfill.py | 100 +++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 evennia/contrib/fieldfill.py diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py new file mode 100644 index 0000000000..91a91fc31d --- /dev/null +++ b/evennia/contrib/fieldfill.py @@ -0,0 +1,100 @@ +""" +Fyield Fyill +""" + +from evennia.utils import evmenu, evtable +from evennia import Command + +""" +Complete field data is sent to the given callable as a dictionary (field:value pairs) + +FORM LIST/DICTIONARY VALUES: +Required: + fieldname - Name of the field as presented to the player + fieldtype - Type of field, either 'text' or 'number' + +Optional: + max - Maximum character length (if text) or value (if number) + min - Minimum charater length (if text) or value (if number) + default - Initial value (blank if not given) + blankmsg - Message to show when field is blank +""" + +SAMPLE_FORM = [ +{"fieldname":"Player", "fieldtype":"text", "max":30, "default":"Ashley"}, +{"fieldname":"Delay", "fieldtype":"number", "min":3, "max":30, "default":10}, +{"fieldname":"Message", "fieldtype":"text", "min":3, "max":200, +"default": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non urna ante. Etiam maximus orci ut commodo lobortis. Sed sodales sed libero quis fermentum. Nunc vel semper ante. Donec mattis nisl eget condimentum mattis. Pellentesque ac semper lorem. Sed augue." +} +] + +def init_fill_field(form, caller, callback): + """ + Presents a player with a fillable form. + """ + + # Pass kwargs to store data needed in the menu + kwargs = { + "formdata":form_template_to_dict(form_template) + } + + # Initialize menu of selections + evmenu.EvMenu(caller, "evennia.contrib.fieldfill", startnode="menunode_fieldfill", **kwargs) + + +def menunode_fieldfill(caller, raw_string, **kwargs): + """ + Repeating node to fill a menu field + """ + # Retrieve menu info + formdata = caller.ndb._menutree.formdata + + +def form_template_to_dict(formtemplate): + """ + Returns dictionary of field name:value pairs from form template + """ + formdict = {} + + for field in formtemplate: + fieldvalue = "" + if "default" in field: + fieldvalue = field["default"] + formdict.update({field["fieldname"]:fieldvalue}) + + return formdict + +def display_formdata(formtemplate, formdata): + """ + Displays a form's current data as a table + """ + formtable = evtable.EvTable(border="cells") + field_name_width = 3 + + for field in formtemplate: + new_fieldname = "" + new_fieldvalue = "" + # Get field name + new_fieldname = "|w" + field["fieldname"] + ":|n" + if len(field["fieldname"]) + 5 > field_name_width: + field_name_width = len(field["fieldname"]) + 5 + # Get field value + new_fieldvalue = str(formdata[field["fieldname"]]) + # Add name and value to table + formtable.add_row(new_fieldname, new_fieldvalue) + + formtable.reformat_column(0, align="r", width=field_name_width) + formtable.reformat(valign="t", width=80) + + return formtable + +class CmdTest(Command): + """ + Test stuff + """ + + key = "test" + + def func(self): + SAMPLE_FORM_DATA = form_template_to_dict(SAMPLE_FORM) + self.caller.msg(display_formdata(SAMPLE_FORM, SAMPLE_FORM_DATA)) \ No newline at end of file From 36643a7ecdd722bb766738af9d4f4bf2fe93e9b6 Mon Sep 17 00:00:00 2001 From: FlutterSprite Date: Thu, 31 May 2018 00:45:57 -0700 Subject: [PATCH 02/17] Further developments --- evennia/contrib/fieldfill.py | 58 +++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index 91a91fc31d..4a8110e62e 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -18,24 +18,28 @@ Optional: min - Minimum charater length (if text) or value (if number) default - Initial value (blank if not given) blankmsg - Message to show when field is blank + verifyfunc - Name of a callable used to verify input """ SAMPLE_FORM = [ -{"fieldname":"Player", "fieldtype":"text", "max":30, "default":"Ashley"}, +{"fieldname":"Player", "fieldtype":"text", "max":30, "blankmsg":"(Name of an online player)"}, {"fieldname":"Delay", "fieldtype":"number", "min":3, "max":30, "default":10}, {"fieldname":"Message", "fieldtype":"text", "min":3, "max":200, -"default": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non urna ante. Etiam maximus orci ut commodo lobortis. Sed sodales sed libero quis fermentum. Nunc vel semper ante. Donec mattis nisl eget condimentum mattis. Pellentesque ac semper lorem. Sed augue." +"default": "Lorem ipsum dolor sit amet" } ] -def init_fill_field(form, caller, callback): +def init_fill_field(formtemplate, caller, callback): """ Presents a player with a fillable form. """ + # Initialize form data from the template + blank_formdata = form_template_to_dict(formtemplate) # Pass kwargs to store data needed in the menu kwargs = { - "formdata":form_template_to_dict(form_template) + "formdata":blank_formdata, + "formtemplate": formtemplate } # Initialize menu of selections @@ -48,6 +52,28 @@ def menunode_fieldfill(caller, raw_string, **kwargs): """ # Retrieve menu info formdata = caller.ndb._menutree.formdata + formtemplate = caller.ndb._menutree.formtemplate + + # Display current form data + text = display_formdata(formtemplate, formdata) + options = ({"key": "_default", + "goto":"menunode_fieldfill"}) + + if raw_string: + if raw_string.lower().strip() == "show": + return text, options + elif "=" not in raw_string: + text = None + caller.msg("NO!") + return text, options + else: + entry = raw_string.split("=", 1) + fieldname = entry[0].strip() + newvalue = entry[1].strip() + caller.msg("Setting %s to %s!" % (fieldname, newvalue)) + text = None + + return text, options def form_template_to_dict(formtemplate): @@ -68,8 +94,8 @@ def display_formdata(formtemplate, formdata): """ Displays a form's current data as a table """ - formtable = evtable.EvTable(border="cells") - field_name_width = 3 + formtable = evtable.EvTable(border="rows", valign="t", maxwidth=80) + field_name_width = 5 for field in formtemplate: new_fieldname = "" @@ -80,11 +106,14 @@ def display_formdata(formtemplate, formdata): field_name_width = len(field["fieldname"]) + 5 # Get field value new_fieldvalue = str(formdata[field["fieldname"]]) + # Use blank message if field is blank and once is present + if new_fieldvalue == "" and "blankmsg" in field: + new_fieldvalue = "|x" + str(field["blankmsg"]) + "|n" # Add name and value to table formtable.add_row(new_fieldname, new_fieldvalue) formtable.reformat_column(0, align="r", width=field_name_width) - formtable.reformat(valign="t", width=80) + formtable.reformat_column(1, pad_left=0) return formtable @@ -97,4 +126,17 @@ class CmdTest(Command): def func(self): SAMPLE_FORM_DATA = form_template_to_dict(SAMPLE_FORM) - self.caller.msg(display_formdata(SAMPLE_FORM, SAMPLE_FORM_DATA)) \ No newline at end of file + self.caller.msg(display_formdata(SAMPLE_FORM, SAMPLE_FORM_DATA)) + +class CmdTestMenu(Command): + """ + Test stuff + """ + + key = "testmenu" + + def func(self): + init_fill_field(SAMPLE_FORM, self.caller, Placeholder) + +def Placeholder(): + return \ No newline at end of file From 3768624a0904cb095a27d56a81f240a457b0bfdc Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Thu, 31 May 2018 17:45:00 -0700 Subject: [PATCH 03/17] Basic filling functionality implemented --- evennia/contrib/fieldfill.py | 164 ++++++++++++++++++++++++++++++----- 1 file changed, 144 insertions(+), 20 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index 4a8110e62e..ffc555bf88 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -19,16 +19,39 @@ Optional: default - Initial value (blank if not given) blankmsg - Message to show when field is blank verifyfunc - Name of a callable used to verify input + preformtxt - Text to put before the whole form table. Can be put in any field. + postformtxt - Text to put after the whole form table. Can be put in any field. """ SAMPLE_FORM = [ -{"fieldname":"Player", "fieldtype":"text", "max":30, "blankmsg":"(Name of an online player)"}, +{"fieldname":"Player", "fieldtype":"text", "max":30, "blankmsg":"(Name of an online player)", + "preformtxt":"Send a delayed message to another player:", "postformtxt":"Syntax: = |/Or: clear , help, show, quit"}, {"fieldname":"Delay", "fieldtype":"number", "min":3, "max":30, "default":10}, -{"fieldname":"Message", "fieldtype":"text", "min":3, "max":200, -"default": "Lorem ipsum dolor sit amet" -} +{"fieldname":"Message", "fieldtype":"text", "min":3, "max":200, "blankmsg":"(Message up to 200 characters)"} ] +class FieldEvMenu(evmenu.EvMenu): + """ + Custom EvMenu type with its own node formatter - removes extraneous lines + """ + + def node_formatter(self, nodetext, optionstext): + """ + Formats the entirety of the node. + + Args: + nodetext (str): The node text as returned by `self.nodetext_formatter`. + optionstext (str): The options display as returned by `self.options_formatter`. + caller (Object, Account or None, optional): The caller of the node. + + Returns: + node (str): The formatted node to display. + + """ + # Only return node text, no options or separators + return nodetext + + def init_fill_field(formtemplate, caller, callback): """ Presents a player with a fillable form. @@ -43,13 +66,16 @@ def init_fill_field(formtemplate, caller, callback): } # Initialize menu of selections - evmenu.EvMenu(caller, "evennia.contrib.fieldfill", startnode="menunode_fieldfill", **kwargs) + FieldEvMenu(caller, "evennia.contrib.fieldfill", startnode="menunode_fieldfill", **kwargs) def menunode_fieldfill(caller, raw_string, **kwargs): """ Repeating node to fill a menu field """ + # Syntax error goes here + syntax_err = "Syntax: = |/Or: clear , help, show, quit" + # Retrieve menu info formdata = caller.ndb._menutree.formdata formtemplate = caller.ndb._menutree.formtemplate @@ -60,17 +86,103 @@ def menunode_fieldfill(caller, raw_string, **kwargs): "goto":"menunode_fieldfill"}) if raw_string: + # Test for 'show' command if raw_string.lower().strip() == "show": return text, options - elif "=" not in raw_string: + # Test for 'clear' command + cleartest = raw_string.lower().strip().split(" ", 1) + if cleartest[0].lower() == "clear": text = None - caller.msg("NO!") + if len(cleartest) < 2: + caller.msg(syntax_err) + return text, options + matched_field = None + + for key in formdata.keys(): + if cleartest[1].lower() in key.lower(): + matched_field = key + + if not matched_field: + caller.msg("Field '%s' does not exist!" % cleartest[1]) + text = None + return text, options + + formdata.update({matched_field:None}) + caller.ndb._menutree.formdata = formdata + caller.msg("Field '%s' cleared." % matched_field) return text, options - else: - entry = raw_string.split("=", 1) - fieldname = entry[0].strip() - newvalue = entry[1].strip() - caller.msg("Setting %s to %s!" % (fieldname, newvalue)) + + if "=" not in raw_string: + text = None + caller.msg(syntax_err) + return text, options + + # Extract field name and new field value + entry = raw_string.split("=", 1) + fieldname = entry[0].strip() + newvalue = entry[1].strip() + + # Syntax error of field name is too short or blank + if len(fieldname) < 3: + caller.msg(syntax_err) + text = None + return text, options + + # Attempt to match field name to field in form data + matched_field = None + for key in formdata.keys(): + if fieldname.lower() in key.lower(): + matched_field = key + + # No matched field + if matched_field == None: + caller.msg("Field '%s' does not exist!" % fieldname) + text = None + return text, options + + # Set new field value if match + # Get data from template + fieldtype = None + max_value = None + min_value = None + for field in formtemplate: + if field["fieldname"] == matched_field: + fieldtype = field["fieldtype"] + if "max" in field.keys(): + max_value = field["max"] + if "min" in field.keys(): + min_value = field["min"] + + # Field type text update + if fieldtype == "text": + # Test for max/min + if max_value != None: + if len(newvalue) > max_value: + caller.msg("Field '%s' has a maximum length of %i characters." % (matched_field, max_value)) + text = None + return text, options + if min_value != None: + if len(newvalue) < min_value: + caller.msg("Field '%s' reqiures a minimum length of %i characters." % (matched_field, min_value)) + text = None + return text, options + # Update form data + formdata.update({matched_field:newvalue}) + caller.ndb._menutree.formdata = formdata + caller.msg("Field '%s' set to: %s" % (matched_field, newvalue)) + text = None + + # Field type number update + if fieldtype == "number": + try: + newvalue = int(newvalue) + except: + caller.msg("Field '%s' requires a number." % matched_field) + text = None + return text, options + formdata.update({matched_field:newvalue}) + caller.ndb._menutree.formdata = formdata + caller.msg("Field '%s' set to: %i" % (matched_field, newvalue)) text = None return text, options @@ -83,7 +195,7 @@ def form_template_to_dict(formtemplate): formdict = {} for field in formtemplate: - fieldvalue = "" + fieldvalue = None if "default" in field: fieldvalue = field["default"] formdict.update({field["fieldname"]:fieldvalue}) @@ -94,28 +206,40 @@ def display_formdata(formtemplate, formdata): """ Displays a form's current data as a table """ - formtable = evtable.EvTable(border="rows", valign="t", maxwidth=80) + formtable = evtable.EvTable(border="cells", valign="t", maxwidth=80) field_name_width = 5 for field in formtemplate: - new_fieldname = "" - new_fieldvalue = "" + new_fieldname = None + new_fieldvalue = None # Get field name new_fieldname = "|w" + field["fieldname"] + ":|n" if len(field["fieldname"]) + 5 > field_name_width: field_name_width = len(field["fieldname"]) + 5 # Get field value - new_fieldvalue = str(formdata[field["fieldname"]]) + if formdata[field["fieldname"]] != None: + new_fieldvalue = str(formdata[field["fieldname"]]) # Use blank message if field is blank and once is present - if new_fieldvalue == "" and "blankmsg" in field: + if new_fieldvalue == None and "blankmsg" in field: new_fieldvalue = "|x" + str(field["blankmsg"]) + "|n" + elif new_fieldvalue == None: + new_fieldvalue = " " # Add name and value to table formtable.add_row(new_fieldname, new_fieldvalue) formtable.reformat_column(0, align="r", width=field_name_width) - formtable.reformat_column(1, pad_left=0) + # formtable.reformat_column(1, pad_left=0) + + # Get pre-text and/or post-text + pretext = "" + posttext = "" + for field in formtemplate: + if "preformtxt" in field: + pretext = field["preformtxt"] + "|/" + if "postformtxt" in field: + posttext = "|/" + field["postformtxt"] - return formtable + return pretext + str(formtable) + posttext class CmdTest(Command): """ From 8df904d30eeb810aa08192c92cfd1d279a387c21 Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Fri, 1 Jun 2018 19:49:46 -0700 Subject: [PATCH 04/17] All basic functionality + example working --- evennia/contrib/fieldfill.py | 105 +++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index ffc555bf88..c1fdb9c10f 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -2,8 +2,9 @@ Fyield Fyill """ -from evennia.utils import evmenu, evtable +from evennia.utils import evmenu, evtable, delay from evennia import Command +from evennia.server.sessionhandler import SESSIONS """ Complete field data is sent to the given callable as a dictionary (field:value pairs) @@ -18,17 +19,12 @@ Optional: min - Minimum charater length (if text) or value (if number) default - Initial value (blank if not given) blankmsg - Message to show when field is blank + cantclear - Field can't be cleared if True verifyfunc - Name of a callable used to verify input preformtxt - Text to put before the whole form table. Can be put in any field. postformtxt - Text to put after the whole form table. Can be put in any field. """ -SAMPLE_FORM = [ -{"fieldname":"Player", "fieldtype":"text", "max":30, "blankmsg":"(Name of an online player)", - "preformtxt":"Send a delayed message to another player:", "postformtxt":"Syntax: = |/Or: clear , help, show, quit"}, -{"fieldname":"Delay", "fieldtype":"number", "min":3, "max":30, "default":10}, -{"fieldname":"Message", "fieldtype":"text", "min":3, "max":200, "blankmsg":"(Message up to 200 characters)"} -] class FieldEvMenu(evmenu.EvMenu): """ @@ -62,7 +58,8 @@ def init_fill_field(formtemplate, caller, callback): # Pass kwargs to store data needed in the menu kwargs = { "formdata":blank_formdata, - "formtemplate": formtemplate + "formtemplate": formtemplate, + "callback": callback } # Initialize menu of selections @@ -79,6 +76,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Retrieve menu info formdata = caller.ndb._menutree.formdata formtemplate = caller.ndb._menutree.formtemplate + callback = caller.ndb._menutree.callback # Display current form data text = display_formdata(formtemplate, formdata) @@ -86,6 +84,10 @@ def menunode_fieldfill(caller, raw_string, **kwargs): "goto":"menunode_fieldfill"}) if raw_string: + # Test for 'submit' command + if raw_string.lower().strip() == "submit": + callback(caller, formdata) + return None, None # Test for 'show' command if raw_string.lower().strip() == "show": return text, options @@ -106,7 +108,17 @@ def menunode_fieldfill(caller, raw_string, **kwargs): caller.msg("Field '%s' does not exist!" % cleartest[1]) text = None return text, options - + + # Test to see if field can be cleared + for field in formtemplate: + if field["fieldname"] == matched_field and "cantclear" in field.keys(): + if field["cantclear"] == True: + caller.msg("Field '%s' can't be cleared!" % matched_field) + text = None + return text, options + + + # Clear the field formdata.update({matched_field:None}) caller.ndb._menutree.formdata = formdata caller.msg("Field '%s' cleared." % matched_field) @@ -145,6 +157,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): fieldtype = None max_value = None min_value = None + verifyfunc = None for field in formtemplate: if field["fieldname"] == matched_field: fieldtype = field["fieldtype"] @@ -152,6 +165,9 @@ def menunode_fieldfill(caller, raw_string, **kwargs): max_value = field["max"] if "min" in field.keys(): min_value = field["min"] + if "verifyfunc" in field.keys(): + verifyfunc = field["verifyfunc"] + # Field type text update if fieldtype == "text": @@ -166,11 +182,6 @@ def menunode_fieldfill(caller, raw_string, **kwargs): caller.msg("Field '%s' reqiures a minimum length of %i characters." % (matched_field, min_value)) text = None return text, options - # Update form data - formdata.update({matched_field:newvalue}) - caller.ndb._menutree.formdata = formdata - caller.msg("Field '%s' set to: %s" % (matched_field, newvalue)) - text = None # Field type number update if fieldtype == "number": @@ -180,10 +191,20 @@ def menunode_fieldfill(caller, raw_string, **kwargs): caller.msg("Field '%s' requires a number." % matched_field) text = None return text, options - formdata.update({matched_field:newvalue}) - caller.ndb._menutree.formdata = formdata - caller.msg("Field '%s' set to: %i" % (matched_field, newvalue)) - text = None + + # Call verify function if present + if verifyfunc: + if verifyfunc(caller, newvalue) == False: + text = None + return text, options + elif verifyfunc(caller, newvalue) != True: + newvalue = verifyfunc(caller, newvalue) + + # If everything checks out, update form!! + formdata.update({matched_field:newvalue}) + caller.ndb._menutree.formdata = formdata + caller.msg("Field '%s' set to: %s" % (matched_field, str(newvalue))) + text = None return text, options @@ -241,6 +262,36 @@ def display_formdata(formtemplate, formdata): return pretext + str(formtable) + posttext + + + +# PLACEHOLDER / EXAMPLE STUFF STARTS HEEEERE + +def verify_online_player(caller, value): + # Get a list of sessions + session_list = SESSIONS.get_sessions() + char_list = [] + matched_character = None + for session in session_list: + if not session.logged_in: + continue + char_list.append(session.get_puppet()) + print char_list + for character in char_list: + if value.lower() in character.key.lower(): + matched_character = character + if not matched_character: + caller.msg("No character matching '%s' is online." % value) + return False + return matched_character + +SAMPLE_FORM = [ +{"fieldname":"Player", "fieldtype":"text", "max":30, "blankmsg":"(Name of an online player)", "verifyfunc":verify_online_player, + "preformtxt":"Send a delayed message to another player:", "postformtxt":"Syntax: = |/Or: clear , help, show, quit"}, +{"fieldname":"Delay", "fieldtype":"number", "min":3, "max":30, "default":10, "cantclear":True}, +{"fieldname":"Message", "fieldtype":"text", "min":3, "max":200, "blankmsg":"(Message up to 200 characters)"} +] + class CmdTest(Command): """ Test stuff @@ -260,7 +311,17 @@ class CmdTestMenu(Command): key = "testmenu" def func(self): - init_fill_field(SAMPLE_FORM, self.caller, Placeholder) - -def Placeholder(): - return \ No newline at end of file + init_fill_field(SAMPLE_FORM, self.caller, init_delayed_message) + +def sendmessage(obj, text): + obj.msg(text) + +def init_delayed_message(caller, formdata): + player_to_message = formdata["Player"] + message_delay = formdata["Delay"] + message = ("Message from %s: " % caller) + formdata["Message"] + + deferred = delay(message_delay, sendmessage, player_to_message, message) + + return + From b0402e47dfb2c76393ccbdcc9a1629d5c9e557a8 Mon Sep 17 00:00:00 2001 From: FlutterSprite Date: Sun, 3 Jun 2018 02:52:16 -0700 Subject: [PATCH 05/17] More functionality + cleanup --- evennia/contrib/fieldfill.py | 128 ++++++++++++++++++++++++----------- 1 file changed, 89 insertions(+), 39 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index c1fdb9c10f..071558c88c 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -2,7 +2,7 @@ Fyield Fyill """ -from evennia.utils import evmenu, evtable, delay +from evennia.utils import evmenu, evtable, delay, list_to_string from evennia import Command from evennia.server.sessionhandler import SESSIONS @@ -20,9 +20,8 @@ Optional: default - Initial value (blank if not given) blankmsg - Message to show when field is blank cantclear - Field can't be cleared if True + required - If True, form cannot be submitted while field is blank verifyfunc - Name of a callable used to verify input - preformtxt - Text to put before the whole form table. Can be put in any field. - postformtxt - Text to put after the whole form table. Can be put in any field. """ @@ -48,7 +47,7 @@ class FieldEvMenu(evmenu.EvMenu): return nodetext -def init_fill_field(formtemplate, caller, callback): +def init_fill_field(formtemplate, caller, callback, pretext="", posttext="", submitcmd="submit", borderstyle="cells"): """ Presents a player with a fillable form. """ @@ -59,7 +58,11 @@ def init_fill_field(formtemplate, caller, callback): kwargs = { "formdata":blank_formdata, "formtemplate": formtemplate, - "callback": callback + "callback": callback, + "pretext": pretext, + "posttext": posttext, + "submitcmd": submitcmd, + "borderstyle": borderstyle } # Initialize menu of selections @@ -70,31 +73,62 @@ def menunode_fieldfill(caller, raw_string, **kwargs): """ Repeating node to fill a menu field """ - # Syntax error goes here - syntax_err = "Syntax: = |/Or: clear , help, show, quit" # Retrieve menu info formdata = caller.ndb._menutree.formdata formtemplate = caller.ndb._menutree.formtemplate callback = caller.ndb._menutree.callback + pretext = caller.ndb._menutree.pretext + posttext = caller.ndb._menutree.posttext + submitcmd = caller.ndb._menutree.submitcmd + borderstyle = caller.ndb._menutree.borderstyle + + # Syntax error + syntax_err = "Syntax: = |/Or: clear , help, show, quit|/'%s' to submit form" % submitcmd + + # Set help text, including listing the 'submit' command + help_text = """Available commands: +|w = :|n Set given field to new value, replacing the old value +|wclear :|n Clear the value in the given field, making it blank +|wshow|n: Show the form's current values +|whelp|n: Display this help screen +|wquit|n: Quit the form menu without submitting +|w%s|n: Submit this form and quit the menu""" % submitcmd # Display current form data - text = display_formdata(formtemplate, formdata) + text = (display_formdata(formtemplate, formdata, pretext=pretext, + posttext=posttext, borderstyle=borderstyle), help_text) options = ({"key": "_default", "goto":"menunode_fieldfill"}) if raw_string: - # Test for 'submit' command - if raw_string.lower().strip() == "submit": + # Test for given 'submit' command + if raw_string.lower().strip() == submitcmd: + # Test to see if any blank fields are required + blank_and_required = [] + for field in formtemplate: + if "required" in field.keys(): + # If field is required but current form data for field is blank + if field["required"] == True and formdata[field["fieldname"]] == None: + # Add to blank and required fields + blank_and_required.append(field["fieldname"]) + if len(blank_and_required) > 0: + caller.msg("The following blank fields require a value: %s" % list_to_string(blank_and_required)) + text = (None, help_text) + return text, options + + # If everything checks out, pass form data to the callback and end the menu! callback(caller, formdata) return None, None + # Test for 'show' command if raw_string.lower().strip() == "show": return text, options + # Test for 'clear' command cleartest = raw_string.lower().strip().split(" ", 1) if cleartest[0].lower() == "clear": - text = None + text = (None, help_text) if len(cleartest) < 2: caller.msg(syntax_err) return text, options @@ -106,7 +140,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): if not matched_field: caller.msg("Field '%s' does not exist!" % cleartest[1]) - text = None + text = (None, help_text) return text, options # Test to see if field can be cleared @@ -114,7 +148,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): if field["fieldname"] == matched_field and "cantclear" in field.keys(): if field["cantclear"] == True: caller.msg("Field '%s' can't be cleared!" % matched_field) - text = None + text = (None, help_text) return text, options @@ -125,7 +159,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): return text, options if "=" not in raw_string: - text = None + text = (None, help_text) caller.msg(syntax_err) return text, options @@ -137,7 +171,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Syntax error of field name is too short or blank if len(fieldname) < 3: caller.msg(syntax_err) - text = None + text = (None, help_text) return text, options # Attempt to match field name to field in form data @@ -149,7 +183,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # No matched field if matched_field == None: caller.msg("Field '%s' does not exist!" % fieldname) - text = None + text = (None, help_text) return text, options # Set new field value if match @@ -175,12 +209,12 @@ def menunode_fieldfill(caller, raw_string, **kwargs): if max_value != None: if len(newvalue) > max_value: caller.msg("Field '%s' has a maximum length of %i characters." % (matched_field, max_value)) - text = None + text = (None, help_text) return text, options if min_value != None: if len(newvalue) < min_value: caller.msg("Field '%s' reqiures a minimum length of %i characters." % (matched_field, min_value)) - text = None + text = (None, help_text) return text, options # Field type number update @@ -189,13 +223,13 @@ def menunode_fieldfill(caller, raw_string, **kwargs): newvalue = int(newvalue) except: caller.msg("Field '%s' requires a number." % matched_field) - text = None + text = (None, help_text) return text, options # Call verify function if present if verifyfunc: if verifyfunc(caller, newvalue) == False: - text = None + text = (None, help_text) return text, options elif verifyfunc(caller, newvalue) != True: newvalue = verifyfunc(caller, newvalue) @@ -204,7 +238,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): formdata.update({matched_field:newvalue}) caller.ndb._menutree.formdata = formdata caller.msg("Field '%s' set to: %s" % (matched_field, str(newvalue))) - text = None + text = (None, help_text) return text, options @@ -223,11 +257,13 @@ def form_template_to_dict(formtemplate): return formdict -def display_formdata(formtemplate, formdata): +def display_formdata(formtemplate, formdata, + pretext="", posttext="", borderstyle="cells"): """ Displays a form's current data as a table """ - formtable = evtable.EvTable(border="cells", valign="t", maxwidth=80) + + formtable = evtable.EvTable(border=borderstyle, valign="t", maxwidth=80) field_name_width = 5 for field in formtemplate: @@ -250,17 +286,8 @@ def display_formdata(formtemplate, formdata): formtable.reformat_column(0, align="r", width=field_name_width) # formtable.reformat_column(1, pad_left=0) - - # Get pre-text and/or post-text - pretext = "" - posttext = "" - for field in formtemplate: - if "preformtxt" in field: - pretext = field["preformtxt"] + "|/" - if "postformtxt" in field: - posttext = "|/" + field["postformtxt"] - return pretext + str(formtable) + posttext + return pretext + "|/" + str(formtable) + "|/" + posttext @@ -272,22 +299,35 @@ def verify_online_player(caller, value): session_list = SESSIONS.get_sessions() char_list = [] matched_character = None + + # Get a list of online characters for session in session_list: if not session.logged_in: + # Skip over logged out characters continue + # Append to our list of online characters otherwise char_list.append(session.get_puppet()) - print char_list + + # Match player input to a character name for character in char_list: - if value.lower() in character.key.lower(): + if value.lower() == character.key.lower(): matched_character = character + + # If input didn't match to a character if not matched_character: + # Send the player an error message unique to this function caller.msg("No character matching '%s' is online." % value) + # Returning False indicates the new value is not valid return False + + # Returning anything besides True or False will replace the player's input with the returned value + # In this case, the value becomes a reference to the character object + # You can store data besides strings and integers in the 'formdata' dictionary this way! return matched_character SAMPLE_FORM = [ -{"fieldname":"Player", "fieldtype":"text", "max":30, "blankmsg":"(Name of an online player)", "verifyfunc":verify_online_player, - "preformtxt":"Send a delayed message to another player:", "postformtxt":"Syntax: = |/Or: clear , help, show, quit"}, +{"fieldname":"Character", "fieldtype":"text", "max":30, "blankmsg":"(Name of an online player)", + "required":True, "verifyfunc":verify_online_player}, {"fieldname":"Delay", "fieldtype":"number", "min":3, "max":30, "default":10, "cantclear":True}, {"fieldname":"Message", "fieldtype":"text", "min":3, "max":200, "blankmsg":"(Message up to 200 characters)"} ] @@ -311,16 +351,26 @@ class CmdTestMenu(Command): key = "testmenu" def func(self): - init_fill_field(SAMPLE_FORM, self.caller, init_delayed_message) + + pretext = "|cSend a delayed message to another player ---------------------------------------|n" + posttext = ("|c--------------------------------------------------------------------------------|n|/" + "Syntax: type |c = |n to change the values of the form. Given|/" + "player must be currently logged in, delay is given in seconds. When you are|/" + "finished, type '|csend|n' to send the message.|/") + + init_fill_field(SAMPLE_FORM, self.caller, init_delayed_message, + pretext=pretext, posttext=posttext, + submitcmd="send", borderstyle="none") def sendmessage(obj, text): obj.msg(text) def init_delayed_message(caller, formdata): - player_to_message = formdata["Player"] + player_to_message = formdata["Character"] message_delay = formdata["Delay"] message = ("Message from %s: " % caller) + formdata["Message"] + caller.msg("Message sent to %s!" % player_to_message) deferred = delay(message_delay, sendmessage, player_to_message, message) return From 20eda1e88fa1817f4c3905c0500782591d1e5f41 Mon Sep 17 00:00:00 2001 From: FlutterSprite Date: Wed, 6 Jun 2018 17:14:50 -0700 Subject: [PATCH 06/17] Polish and such --- evennia/contrib/fieldfill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index 071558c88c..312ddc658b 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -169,7 +169,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): newvalue = entry[1].strip() # Syntax error of field name is too short or blank - if len(fieldname) < 3: + if len(fieldname) < 1: caller.msg(syntax_err) text = (None, help_text) return text, options From e3b562e2100c60bc0993490dcf5a423927020800 Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Wed, 6 Jun 2018 18:05:26 -0700 Subject: [PATCH 07/17] Documentation, min/max actually works on numbers now --- evennia/contrib/fieldfill.py | 174 +++++++++++++++++++++++++++++------ 1 file changed, 145 insertions(+), 29 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index 312ddc658b..7a41e52025 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -1,30 +1,134 @@ """ -Fyield Fyill +Easy fillable form + +Contrib - Tim Ashley Jenkins 2018 + +This module contains a function that calls an easily customizable EvMenu - this +menu presents the player with a fillable form, with fields that can be filled +out in any order. Each field's value can be verified, with the function +allowing easy checks for text and integer input, minimum and maximum values / +character lengths, or can even be verified by a custom function. Once the form +is submitted, the form's data is submitted as a dictionary to any callable of +your choice. + +Form templates are defined as a list of dictionaries - each dictionary +represents a field in the form, and contains the data for the field's name and +behavior. For example, this basic form template will allow a player to fill out +a brief character profile: + + PROFILE_TEMPLATE = [ + {"fieldname":"Name", "fieldtype":"text"}, + {"fieldname":"Age", "fieldtype":"number"}, + {"fieldname":"History", "fieldtype":"text"} + ] + +This will present the player with an EvMenu showing this basic form: + + Name: + Age: + History: + +While in this menu, the player can assign a new value to any field with the +syntax = , like so: + + > name = Ashley + Field 'Name' set to: Ashley + +Typing 'show' by itself will show the form and its current values. + + > show + + Name: Ashley + Age: + History: + +Number fields require an integer input, and will reject any text that can't +be converted into an integer. + + > age = youthful + Field 'Age' requires a number. + > age = 31 + Field 'Age' set to: 31 + +Form data is presented as an EvTable, so text of any length will wrap cleanly. + + > history = EVERY MORNING I WAKE UP AND OPEN PALM SLAM[...] + Field 'History' set to: EVERY MORNING I WAKE UP AND[...] + > show + + Name: Ashley + Age: 31 + History: EVERY MORNING I WAKE UP AND OPEN PALM SLAM A VHS INTO THE SLOT. + IT'S CHRONICLES OF RIDDICK AND RIGHT THEN AND THERE I START DOING + THE MOVES ALONGSIDE WITH THE MAIN CHARACTER, RIDDICK. I DO EVERY + MOVE AND I DO EVERY MOVE HARD. + +When the player types 'submit' (or your specified submit command), the menu +quits and the form's data is passed to your specified function as a dictionary, +like so: + + formdata = {"Name":"Ashley", "Age":31, "History":"EVERY MORNING I[...]"} + +You can do whatever you like with this data in your function - forms can be used +to set data on a character, to help builders create objects, or for players to +craft items or perform other complicated actions with many variables involved. + +The data that your form will accept can also be specified in your form template - +let's say, for example, that you won't accept ages under 18 or over 100. You can +do this by specifying "min" and "max" values in your field's dictionary: + + PROFILE_TEMPLATE = [ + {"fieldname":"Name", "fieldtype":"text"}, + {"fieldname":"Age", "fieldtype":"number", "min":18, "max":100}, + {"fieldname":"History", "fieldtype":"text"} + ] + +Now if the player tries to enter a value out of range, the form will not acept the +given value. + + > age = 10 + Field 'Age' reqiures a minimum value of 18. + > age = 900 + Field 'Age' has a maximum value of 100. + +Setting 'min' and 'max' for a text field will instead act as a minimum or +maximum character length for the player's input. + +There are lots of ways to present the form to the player - fields can have default +values or show a custom message in place of a blank value, and player input can be +verified by a custom function, allowing for a great deal of flexibility. + +This module contains a simple example form that demonstrates all of the included +functionality - a command that allows a player to compose a message to another +online character and have it send after a custom delay. You can test it by +importing this module in your game's default_cmdsets.py module and adding +CmdTestMenu to your default character's command set. + +FIELD TEMPLATE KEYS: +Required: + fieldname (str): Name of the field, as presented to the player + fieldtype (str):Type of value required, either 'text' or 'number' + +Optional: + max (int): Maximum character length (if text) or value (if number) + min (int): Minimum charater length (if text) or value (if number) + default (str): Initial value (blank if not given) + blankmsg (str): Message to show in place of value when field is blank + cantclear (bool): Field can't be cleared if True + required (bool): If True, form cannot be submitted while field is blank + verifyfunc (callable): Name of a callable used to verify input - takes + (caller, value) as arguments. If the function returns True, + the player's input is considered valid - if it returns False, + the input is rejected. Any other value returned will act as + the field's new value, replacing the player's input. This + allows for values that aren't strings or integers (such as + object dbrefs). """ from evennia.utils import evmenu, evtable, delay, list_to_string from evennia import Command from evennia.server.sessionhandler import SESSIONS -""" -Complete field data is sent to the given callable as a dictionary (field:value pairs) - -FORM LIST/DICTIONARY VALUES: -Required: - fieldname - Name of the field as presented to the player - fieldtype - Type of field, either 'text' or 'number' - -Optional: - max - Maximum character length (if text) or value (if number) - min - Minimum charater length (if text) or value (if number) - default - Initial value (blank if not given) - blankmsg - Message to show when field is blank - cantclear - Field can't be cleared if True - required - If True, form cannot be submitted while field is blank - verifyfunc - Name of a callable used to verify input -""" - - class FieldEvMenu(evmenu.EvMenu): """ Custom EvMenu type with its own node formatter - removes extraneous lines @@ -47,7 +151,8 @@ class FieldEvMenu(evmenu.EvMenu): return nodetext -def init_fill_field(formtemplate, caller, callback, pretext="", posttext="", submitcmd="submit", borderstyle="cells"): +def init_fill_field(formtemplate, caller, callback, pretext="", posttext="", + submitcmd="submit", borderstyle="cells"): """ Presents a player with a fillable form. """ @@ -86,14 +191,14 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Syntax error syntax_err = "Syntax: = |/Or: clear , help, show, quit|/'%s' to submit form" % submitcmd - # Set help text, including listing the 'submit' command - help_text = """Available commands: -|w = :|n Set given field to new value, replacing the old value -|wclear :|n Clear the value in the given field, making it blank -|wshow|n: Show the form's current values -|whelp|n: Display this help screen -|wquit|n: Quit the form menu without submitting -|w%s|n: Submit this form and quit the menu""" % submitcmd + # Set help text, including listing the given 'submit' command + help_text = ("Available commands:|/" + "|w = :|n Set given field to new value, replacing the old value|/" + "|wclear :|n Clear the value in the given field, making it blank|/" + "|wshow|n: Show the form's current values|/" + "|whelp|n: Display this help screen|/" + "|wquit|n: Quit the form menu without submitting|/" + "|w%s|n: Submit this form and quit the menu" % submitcmd) # Display current form data text = (display_formdata(formtemplate, formdata, pretext=pretext, @@ -225,6 +330,17 @@ def menunode_fieldfill(caller, raw_string, **kwargs): caller.msg("Field '%s' requires a number." % matched_field) text = (None, help_text) return text, options + # Test for max/min + if max_value != None: + if newvalue > max_value: + caller.msg("Field '%s' has a maximum value of %i." % (matched_field, max_value)) + text = (None, help_text) + return text, options + if min_value != None: + if newvalue < min_value: + caller.msg("Field '%s' reqiures a minimum value of %i." % (matched_field, min_value)) + text = (None, help_text) + return text, options # Call verify function if present if verifyfunc: From 07403352af36adcd208cd87376863d2f22cc6a5f Mon Sep 17 00:00:00 2001 From: FlutterSprite Date: Thu, 7 Jun 2018 00:13:35 -0700 Subject: [PATCH 08/17] Finished documentation --- evennia/contrib/fieldfill.py | 180 ++++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 56 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index 7a41e52025..e88dc94139 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -152,13 +152,37 @@ class FieldEvMenu(evmenu.EvMenu): def init_fill_field(formtemplate, caller, callback, pretext="", posttext="", - submitcmd="submit", borderstyle="cells"): + submitcmd="submit", borderstyle="cells", helptext=None): """ - Presents a player with a fillable form. + Initializes a menu presenting a player with a fillable form - once the form + is submitted, the data will be passed as a dictionary to your chosen + function. + + Args: + formtemplate (list of dicts): The template for the form's fields + caller (obj): Player who will be filling out the form + callback (callable): Function to pass the completed form's data to + + Options: + pretext (str): Text to put before the form in the menu + posttext (str): Text to put after the form in the menu + submitcmd (str): Command used to submit the form + borderstyle (str): Form's EvTable border style + helptext (str): Help text for the form menu (or default is provided) """ # Initialize form data from the template blank_formdata = form_template_to_dict(formtemplate) + # Provide default help text if none given + if helptext == None: + helptext = ("Available commands:|/" + "|w = :|n Set given field to new value, replacing the old value|/" + "|wclear :|n Clear the value in the given field, making it blank|/" + "|wshow|n: Show the form's current values|/" + "|whelp|n: Display this help screen|/" + "|wquit|n: Quit the form menu without submitting|/" + "|w%s|n: Submit this form and quit the menu" % submitcmd) + # Pass kwargs to store data needed in the menu kwargs = { "formdata":blank_formdata, @@ -168,6 +192,7 @@ def init_fill_field(formtemplate, caller, callback, pretext="", posttext="", "posttext": posttext, "submitcmd": submitcmd, "borderstyle": borderstyle + "helptext": helptext } # Initialize menu of selections @@ -176,7 +201,9 @@ def init_fill_field(formtemplate, caller, callback, pretext="", posttext="", def menunode_fieldfill(caller, raw_string, **kwargs): """ - Repeating node to fill a menu field + This is an EvMenu node, which calls itself over and over in order to + allow a player to enter values into a fillable form. When the form is + submitted, the form data is passed to a callback as a dictionary. """ # Retrieve menu info @@ -187,22 +214,14 @@ def menunode_fieldfill(caller, raw_string, **kwargs): posttext = caller.ndb._menutree.posttext submitcmd = caller.ndb._menutree.submitcmd borderstyle = caller.ndb._menutree.borderstyle + helptext = caller.ndb._menutree.helptext # Syntax error syntax_err = "Syntax: = |/Or: clear , help, show, quit|/'%s' to submit form" % submitcmd - # Set help text, including listing the given 'submit' command - help_text = ("Available commands:|/" - "|w = :|n Set given field to new value, replacing the old value|/" - "|wclear :|n Clear the value in the given field, making it blank|/" - "|wshow|n: Show the form's current values|/" - "|whelp|n: Display this help screen|/" - "|wquit|n: Quit the form menu without submitting|/" - "|w%s|n: Submit this form and quit the menu" % submitcmd) - # Display current form data text = (display_formdata(formtemplate, formdata, pretext=pretext, - posttext=posttext, borderstyle=borderstyle), help_text) + posttext=posttext, borderstyle=borderstyle), helptext) options = ({"key": "_default", "goto":"menunode_fieldfill"}) @@ -219,7 +238,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): blank_and_required.append(field["fieldname"]) if len(blank_and_required) > 0: caller.msg("The following blank fields require a value: %s" % list_to_string(blank_and_required)) - text = (None, help_text) + text = (None, helptext) return text, options # If everything checks out, pass form data to the callback and end the menu! @@ -233,7 +252,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Test for 'clear' command cleartest = raw_string.lower().strip().split(" ", 1) if cleartest[0].lower() == "clear": - text = (None, help_text) + text = (None, helptext) if len(cleartest) < 2: caller.msg(syntax_err) return text, options @@ -245,7 +264,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): if not matched_field: caller.msg("Field '%s' does not exist!" % cleartest[1]) - text = (None, help_text) + text = (None, helptext) return text, options # Test to see if field can be cleared @@ -253,9 +272,8 @@ def menunode_fieldfill(caller, raw_string, **kwargs): if field["fieldname"] == matched_field and "cantclear" in field.keys(): if field["cantclear"] == True: caller.msg("Field '%s' can't be cleared!" % matched_field) - text = (None, help_text) + text = (None, helptext) return text, options - # Clear the field formdata.update({matched_field:None}) @@ -264,7 +282,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): return text, options if "=" not in raw_string: - text = (None, help_text) + text = (None, helptext) caller.msg(syntax_err) return text, options @@ -276,7 +294,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Syntax error of field name is too short or blank if len(fieldname) < 1: caller.msg(syntax_err) - text = (None, help_text) + text = (None, helptext) return text, options # Attempt to match field name to field in form data @@ -288,7 +306,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # No matched field if matched_field == None: caller.msg("Field '%s' does not exist!" % fieldname) - text = (None, help_text) + text = (None, helptext) return text, options # Set new field value if match @@ -306,7 +324,6 @@ def menunode_fieldfill(caller, raw_string, **kwargs): min_value = field["min"] if "verifyfunc" in field.keys(): verifyfunc = field["verifyfunc"] - # Field type text update if fieldtype == "text": @@ -314,38 +331,39 @@ def menunode_fieldfill(caller, raw_string, **kwargs): if max_value != None: if len(newvalue) > max_value: caller.msg("Field '%s' has a maximum length of %i characters." % (matched_field, max_value)) - text = (None, help_text) + text = (None, helptext) return text, options if min_value != None: if len(newvalue) < min_value: caller.msg("Field '%s' reqiures a minimum length of %i characters." % (matched_field, min_value)) - text = (None, help_text) + text = (None, helptext) return text, options - + # Field type number update if fieldtype == "number": try: newvalue = int(newvalue) except: caller.msg("Field '%s' requires a number." % matched_field) - text = (None, help_text) + text = (None, helptext) return text, options # Test for max/min if max_value != None: if newvalue > max_value: caller.msg("Field '%s' has a maximum value of %i." % (matched_field, max_value)) - text = (None, help_text) + text = (None, helptext) return text, options if min_value != None: if newvalue < min_value: caller.msg("Field '%s' reqiures a minimum value of %i." % (matched_field, min_value)) - text = (None, help_text) + text = (None, helptext) return text, options - + # Call verify function if present if verifyfunc: if verifyfunc(caller, newvalue) == False: - text = (None, help_text) + # No error message is given - should be provided by verifyfunc + text = (None, helptext) return text, options elif verifyfunc(caller, newvalue) != True: newvalue = verifyfunc(caller, newvalue) @@ -354,29 +372,46 @@ def menunode_fieldfill(caller, raw_string, **kwargs): formdata.update({matched_field:newvalue}) caller.ndb._menutree.formdata = formdata caller.msg("Field '%s' set to: %s" % (matched_field, str(newvalue))) - text = (None, help_text) + text = (None, helptext) return text, options - def form_template_to_dict(formtemplate): """ - Returns dictionary of field name:value pairs from form template + Initializes a dictionary of form data from the given list-of-dictionaries + form template, as formatted above. + + Args: + formtemplate (list of dicts): Tempate for the form to be initialized + + Returns: + formdata (dict): Dictionary of initalized form data """ - formdict = {} + formdata = {} for field in formtemplate: + # Value is blank by default fieldvalue = None if "default" in field: + # Add in default value if present fieldvalue = field["default"] - formdict.update({field["fieldname"]:fieldvalue}) + formdata.update({field["fieldname"]:fieldvalue}) - return formdict + return formdata def display_formdata(formtemplate, formdata, pretext="", posttext="", borderstyle="cells"): """ - Displays a form's current data as a table + Displays a form's current data as a table. Used in the form menu. + + Args: + formtemplate (list of dicts): Template for the form + formdata (dict): Form's current data + + Options: + pretext (str): Text to put before the form table + posttext (str): Text to put after the form table + borderstyle (str): EvTable's border style """ formtable = evtable.EvTable(border=borderstyle, valign="t", maxwidth=80) @@ -404,13 +439,26 @@ def display_formdata(formtemplate, formdata, # formtable.reformat_column(1, pad_left=0) return pretext + "|/" + str(formtable) + "|/" + posttext - - - - -# PLACEHOLDER / EXAMPLE STUFF STARTS HEEEERE + +""" +EXAMPLE FUNCTIONS / COMMAND STARTS HERE +""" def verify_online_player(caller, value): + """ + Example 'verify function' that matches player input to an online character + or else rejects their input as invalid. + + Args: + caller (obj): Player entering the form data + value (str): String player entered into the form, to be verified + + Returns: + matched_character (obj or False): dbref to a currently logged in + character object - reference to the object will be stored in + the form instead of a string. Returns False if no match is + made. + """ # Get a list of sessions session_list = SESSIONS.get_sessions() char_list = [] @@ -441,33 +489,36 @@ def verify_online_player(caller, value): # You can store data besides strings and integers in the 'formdata' dictionary this way! return matched_character +# Form template for the example 'delayed message' form SAMPLE_FORM = [ {"fieldname":"Character", "fieldtype":"text", "max":30, "blankmsg":"(Name of an online player)", "required":True, "verifyfunc":verify_online_player}, {"fieldname":"Delay", "fieldtype":"number", "min":3, "max":30, "default":10, "cantclear":True}, {"fieldname":"Message", "fieldtype":"text", "min":3, "max":200, "blankmsg":"(Message up to 200 characters)"} ] - -class CmdTest(Command): - """ - Test stuff - """ - - key = "test" - - def func(self): - SAMPLE_FORM_DATA = form_template_to_dict(SAMPLE_FORM) - self.caller.msg(display_formdata(SAMPLE_FORM, SAMPLE_FORM_DATA)) class CmdTestMenu(Command): """ - Test stuff + This test command will initialize a menu that presents you with a form. + You can fill out the fields of this form in any order, and then type in + 'send' to send a message to another online player, which will reach them + after a delay you specify. + + Usage: + = + clear + help + show + quit + send """ key = "testmenu" def func(self): - + """ + This performs the actual command. + """ pretext = "|cSend a delayed message to another player ---------------------------------------|n" posttext = ("|c--------------------------------------------------------------------------------|n|/" "Syntax: type |c = |n to change the values of the form. Given|/" @@ -479,15 +530,32 @@ class CmdTestMenu(Command): submitcmd="send", borderstyle="none") def sendmessage(obj, text): + """ + Callback to send a message to a player. + + Args: + obj (obj): Player to message + text (str): Message + """ obj.msg(text) def init_delayed_message(caller, formdata): + """ + Initializes a delayed message, using data from the example form. + + Args: + caller (obj): Character submitting the message + formdata (dict): Data from submitted form + """ + # Retrieve data from the filled out form. + # We stored the character to message as an object ref using a verifyfunc + # So we don't have to do any more searching or matching here! player_to_message = formdata["Character"] message_delay = formdata["Delay"] message = ("Message from %s: " % caller) + formdata["Message"] caller.msg("Message sent to %s!" % player_to_message) + # Make a deferred call to 'sendmessage' above. deferred = delay(message_delay, sendmessage, player_to_message, message) - return From a96a896b5ba2c7ffe0a406f16415169826c21da3 Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Thu, 7 Jun 2018 14:27:39 -0700 Subject: [PATCH 09/17] 'show' changed to 'look' --- evennia/contrib/fieldfill.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index e88dc94139..3090e4fbd1 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -34,9 +34,9 @@ syntax = , like so: > name = Ashley Field 'Name' set to: Ashley -Typing 'show' by itself will show the form and its current values. +Typing 'look' by itself will show the form and its current values. - > show + > look Name: Ashley Age: @@ -54,7 +54,7 @@ Form data is presented as an EvTable, so text of any length will wrap cleanly. > history = EVERY MORNING I WAKE UP AND OPEN PALM SLAM[...] Field 'History' set to: EVERY MORNING I WAKE UP AND[...] - > show + > look Name: Ashley Age: 31 @@ -178,7 +178,7 @@ def init_fill_field(formtemplate, caller, callback, pretext="", posttext="", helptext = ("Available commands:|/" "|w = :|n Set given field to new value, replacing the old value|/" "|wclear :|n Clear the value in the given field, making it blank|/" - "|wshow|n: Show the form's current values|/" + "|wlook|n: Show the form's current values|/" "|whelp|n: Display this help screen|/" "|wquit|n: Quit the form menu without submitting|/" "|w%s|n: Submit this form and quit the menu" % submitcmd) @@ -217,7 +217,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): helptext = caller.ndb._menutree.helptext # Syntax error - syntax_err = "Syntax: = |/Or: clear , help, show, quit|/'%s' to submit form" % submitcmd + syntax_err = "Syntax: = |/Or: clear , help, look, quit|/'%s' to submit form" % submitcmd # Display current form data text = (display_formdata(formtemplate, formdata, pretext=pretext, @@ -245,8 +245,8 @@ def menunode_fieldfill(caller, raw_string, **kwargs): callback(caller, formdata) return None, None - # Test for 'show' command - if raw_string.lower().strip() == "show": + # Test for 'look' command + if raw_string.lower().strip() == "look" or raw_string.lower().strip() == "l": return text, options # Test for 'clear' command @@ -508,7 +508,7 @@ class CmdTestMenu(Command): = clear help - show + look quit send """ From dcf2cd778c1f9634d174bad64537ae41c663eb5f Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Thu, 7 Jun 2018 14:53:11 -0700 Subject: [PATCH 10/17] Start unit tests, fixed syntax errors --- evennia/contrib/fieldfill.py | 2 +- evennia/contrib/tests.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index 3090e4fbd1..770591c1eb 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -191,7 +191,7 @@ def init_fill_field(formtemplate, caller, callback, pretext="", posttext="", "pretext": pretext, "posttext": posttext, "submitcmd": submitcmd, - "borderstyle": borderstyle + "borderstyle": borderstyle, "helptext": helptext } diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 60ac50b64d..72b19c2d9e 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1283,6 +1283,25 @@ class TestTreeSelectFunc(EvenniaTest): {'goto': ['menunode_treeselect', {'newindex': 1}], 'key': ['<< Go Back', 'go back', 'back'], 'desc': 'Return to the previous menu.'}] self.assertTrue(tree_select.optlist_to_menuoptions(TREE_MENU_TESTSTR, test_optlist, 2, True, True) == optlist_to_menu_expected_result) +# Test field fill + +from evennia.contrib import fieldfill + +FIELD_TEST_TEMPLATE = [ +{"fieldname":"TextTest", "fieldtype":"text"}, +{"fieldname":"NumberTest", "fieldtype":"number", "blankmsg":"Number here!"}, +{"fieldname":"DefaultText", "fieldtype":"text", "default":"Test"}, +{"fieldname":"DefaultNum", "fieldtype":"number", "default":3} +] + +FIELD_TEST_DATA = {"TextTest":None, "NumberTest":None, "DefaultText":"Test", "DefaultNum":3} + +class TestFieldFillFunc(EvenniaTest): + + def test_field_functions(self): + # Template to dictionary + self.assertTrue(fieldfill.form_template_to_dict(FIELD_TEST_TEMPLATE) == FIELD_TEST_DATA) + # Test of the unixcommand module from evennia.contrib.unixcommand import UnixCommand From 9f9e882de2a50bc103199f63e8b49d56d02cbc8d Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Thu, 7 Jun 2018 15:11:15 -0700 Subject: [PATCH 11/17] Finishing touches --- evennia/contrib/README.md | 3 +++ evennia/contrib/tests.py | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/README.md b/evennia/contrib/README.md index 5ca11b1799..e15344d87c 100644 --- a/evennia/contrib/README.md +++ b/evennia/contrib/README.md @@ -29,6 +29,9 @@ things you want from here into your game folder and change them there. that requires an email to login rather then just name+password. * Extended Room (Griatch 2012) - An expanded Room typeclass with multiple descriptions for time and season as well as details. +* Field Fill (FlutterSprite 2018) - A simple system for creating an + EvMenu that presents a player with a highly customizable fillable + form * GenderSub (Griatch 2015) - Simple example (only) of storing gender on a character and access it in an emote with a custom marker. * Mail (grungies1138 2016) - An in-game mail system for communication. diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 72b19c2d9e..e4ed875466 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1299,7 +1299,6 @@ FIELD_TEST_DATA = {"TextTest":None, "NumberTest":None, "DefaultText":"Test", "De class TestFieldFillFunc(EvenniaTest): def test_field_functions(self): - # Template to dictionary self.assertTrue(fieldfill.form_template_to_dict(FIELD_TEST_TEMPLATE) == FIELD_TEST_DATA) # Test of the unixcommand module From 39ec7c4fd199a935293c32085218243db14704ee Mon Sep 17 00:00:00 2001 From: FlutterSprite Date: Tue, 12 Jun 2018 21:19:17 -0700 Subject: [PATCH 12/17] I'm not falling for that again! Remembered that "== True" and "is True" are different. --- evennia/contrib/fieldfill.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index 770591c1eb..b7e10c63cc 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -233,7 +233,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): for field in formtemplate: if "required" in field.keys(): # If field is required but current form data for field is blank - if field["required"] == True and formdata[field["fieldname"]] == None: + if field["required"] is True and formdata[field["fieldname"]] is None: # Add to blank and required fields blank_and_required.append(field["fieldname"]) if len(blank_and_required) > 0: @@ -270,7 +270,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Test to see if field can be cleared for field in formtemplate: if field["fieldname"] == matched_field and "cantclear" in field.keys(): - if field["cantclear"] == True: + if field["cantclear"] is True: caller.msg("Field '%s' can't be cleared!" % matched_field) text = (None, helptext) return text, options @@ -361,11 +361,11 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Call verify function if present if verifyfunc: - if verifyfunc(caller, newvalue) == False: + if verifyfunc(caller, newvalue) is False: # No error message is given - should be provided by verifyfunc text = (None, helptext) return text, options - elif verifyfunc(caller, newvalue) != True: + elif verifyfunc(caller, newvalue) is not True: newvalue = verifyfunc(caller, newvalue) # If everything checks out, update form!! From 7502065c13125eb40f0cd34e8cc6c1974999c8a2 Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Wed, 13 Jun 2018 18:59:09 -0700 Subject: [PATCH 13/17] Documentation and initial formdata - 'look' broken? --- evennia/contrib/fieldfill.py | 128 ++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 56 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index b7e10c63cc..0dea4448f9 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -11,6 +11,12 @@ character lengths, or can even be verified by a custom function. Once the form is submitted, the form's data is submitted as a dictionary to any callable of your choice. +The function that initializes the fillable form menu is fairly simple, and +includes the caller, the template for the form, and the callback which the form +data will be sent to upon submission: + + init_fill_field(formtemplate, caller, formcallback) + Form templates are defined as a list of dictionaries - each dictionary represents a field in the form, and contains the data for the field's name and behavior. For example, this basic form template will allow a player to fill out @@ -106,16 +112,16 @@ CmdTestMenu to your default character's command set. FIELD TEMPLATE KEYS: Required: - fieldname (str): Name of the field, as presented to the player - fieldtype (str):Type of value required, either 'text' or 'number' + fieldname (str): Name of the field, as presented to the player. + fieldtype (str):Type of value required, either 'text' or 'number'. Optional: - max (int): Maximum character length (if text) or value (if number) - min (int): Minimum charater length (if text) or value (if number) - default (str): Initial value (blank if not given) - blankmsg (str): Message to show in place of value when field is blank - cantclear (bool): Field can't be cleared if True - required (bool): If True, form cannot be submitted while field is blank + max (int): Maximum character length (if text) or value (if number). + min (int): Minimum charater length (if text) or value (if number). + default (str): Initial value (blank if not given). + blankmsg (str): Message to show in place of value when field is blank. + cantclear (bool): Field can't be cleared if True. + required (bool): If True, form cannot be submitted while field is blank. verifyfunc (callable): Name of a callable used to verify input - takes (caller, value) as arguments. If the function returns True, the player's input is considered valid - if it returns False, @@ -151,31 +157,40 @@ class FieldEvMenu(evmenu.EvMenu): return nodetext -def init_fill_field(formtemplate, caller, callback, pretext="", posttext="", - submitcmd="submit", borderstyle="cells", helptext=None): +def init_fill_field(formtemplate, caller, formcallback, pretext="", posttext="", + submitcmd="submit", borderstyle="cells", formhelptext=None, + initial_formdata=None): """ Initializes a menu presenting a player with a fillable form - once the form is submitted, the data will be passed as a dictionary to your chosen function. Args: - formtemplate (list of dicts): The template for the form's fields - caller (obj): Player who will be filling out the form - callback (callable): Function to pass the completed form's data to + formtemplate (list of dicts): The template for the form's fields. + caller (obj): Player who will be filling out the form. + formcallback (callable): Function to pass the completed form's data to. Options: - pretext (str): Text to put before the form in the menu - posttext (str): Text to put after the form in the menu - submitcmd (str): Command used to submit the form - borderstyle (str): Form's EvTable border style - helptext (str): Help text for the form menu (or default is provided) + pretext (str): Text to put before the form in the menu. + posttext (str): Text to put after the form in the menu. + submitcmd (str): Command used to submit the form. + borderstyle (str): Form's EvTable border style. + formhelptext (str): Help text for the form menu (or default is provided). + initial_formdata (dict): Initial data for the form - a blank form with + defaults specified in the template will be generated otherwise. + In the case of a form used to edit properties on an object or a + similar application, you may want to generate the initial form + data dynamically before calling init_fill_field. """ - # Initialize form data from the template - blank_formdata = form_template_to_dict(formtemplate) + + # Initialize form data from the template if none provided + formdata = form_template_to_dict(formtemplate) + if initial_formdata: + formdata = initial_formdata # Provide default help text if none given - if helptext == None: - helptext = ("Available commands:|/" + if formhelptext == None: + formhelptext = ("Available commands:|/" "|w = :|n Set given field to new value, replacing the old value|/" "|wclear :|n Clear the value in the given field, making it blank|/" "|wlook|n: Show the form's current values|/" @@ -185,14 +200,14 @@ def init_fill_field(formtemplate, caller, callback, pretext="", posttext="", # Pass kwargs to store data needed in the menu kwargs = { - "formdata":blank_formdata, + "formdata":formdata, "formtemplate": formtemplate, - "callback": callback, + "formcallback": formcallback, "pretext": pretext, "posttext": posttext, "submitcmd": submitcmd, "borderstyle": borderstyle, - "helptext": helptext + "formhelptext": formhelptext } # Initialize menu of selections @@ -209,19 +224,19 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Retrieve menu info formdata = caller.ndb._menutree.formdata formtemplate = caller.ndb._menutree.formtemplate - callback = caller.ndb._menutree.callback + formcallback = caller.ndb._menutree.formcallback pretext = caller.ndb._menutree.pretext posttext = caller.ndb._menutree.posttext submitcmd = caller.ndb._menutree.submitcmd borderstyle = caller.ndb._menutree.borderstyle - helptext = caller.ndb._menutree.helptext + formhelptext = caller.ndb._menutree.formhelptext # Syntax error syntax_err = "Syntax: = |/Or: clear , help, look, quit|/'%s' to submit form" % submitcmd # Display current form data text = (display_formdata(formtemplate, formdata, pretext=pretext, - posttext=posttext, borderstyle=borderstyle), helptext) + posttext=posttext, borderstyle=borderstyle), formhelptext) options = ({"key": "_default", "goto":"menunode_fieldfill"}) @@ -238,21 +253,22 @@ def menunode_fieldfill(caller, raw_string, **kwargs): blank_and_required.append(field["fieldname"]) if len(blank_and_required) > 0: caller.msg("The following blank fields require a value: %s" % list_to_string(blank_and_required)) - text = (None, helptext) + text = (None, formhelptext) return text, options # If everything checks out, pass form data to the callback and end the menu! - callback(caller, formdata) + formcallback(caller, formdata) return None, None # Test for 'look' command - if raw_string.lower().strip() == "look" or raw_string.lower().strip() == "l": + if raw_string.lower().strip() == "look": + caller.msg(syntax_err) return text, options # Test for 'clear' command cleartest = raw_string.lower().strip().split(" ", 1) if cleartest[0].lower() == "clear": - text = (None, helptext) + text = (None, formhelptext) if len(cleartest) < 2: caller.msg(syntax_err) return text, options @@ -264,7 +280,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): if not matched_field: caller.msg("Field '%s' does not exist!" % cleartest[1]) - text = (None, helptext) + text = (None, formhelptext) return text, options # Test to see if field can be cleared @@ -272,7 +288,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): if field["fieldname"] == matched_field and "cantclear" in field.keys(): if field["cantclear"] is True: caller.msg("Field '%s' can't be cleared!" % matched_field) - text = (None, helptext) + text = (None, formhelptext) return text, options # Clear the field @@ -282,7 +298,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): return text, options if "=" not in raw_string: - text = (None, helptext) + text = (None, formhelptext) caller.msg(syntax_err) return text, options @@ -294,7 +310,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Syntax error of field name is too short or blank if len(fieldname) < 1: caller.msg(syntax_err) - text = (None, helptext) + text = (None, formhelptext) return text, options # Attempt to match field name to field in form data @@ -306,7 +322,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # No matched field if matched_field == None: caller.msg("Field '%s' does not exist!" % fieldname) - text = (None, helptext) + text = (None, formhelptext) return text, options # Set new field value if match @@ -331,12 +347,12 @@ def menunode_fieldfill(caller, raw_string, **kwargs): if max_value != None: if len(newvalue) > max_value: caller.msg("Field '%s' has a maximum length of %i characters." % (matched_field, max_value)) - text = (None, helptext) + text = (None, formhelptext) return text, options if min_value != None: if len(newvalue) < min_value: caller.msg("Field '%s' reqiures a minimum length of %i characters." % (matched_field, min_value)) - text = (None, helptext) + text = (None, formhelptext) return text, options # Field type number update @@ -345,25 +361,25 @@ def menunode_fieldfill(caller, raw_string, **kwargs): newvalue = int(newvalue) except: caller.msg("Field '%s' requires a number." % matched_field) - text = (None, helptext) + text = (None, formhelptext) return text, options # Test for max/min if max_value != None: if newvalue > max_value: caller.msg("Field '%s' has a maximum value of %i." % (matched_field, max_value)) - text = (None, helptext) + text = (None, formhelptext) return text, options if min_value != None: if newvalue < min_value: caller.msg("Field '%s' reqiures a minimum value of %i." % (matched_field, min_value)) - text = (None, helptext) + text = (None, formhelptext) return text, options # Call verify function if present if verifyfunc: if verifyfunc(caller, newvalue) is False: # No error message is given - should be provided by verifyfunc - text = (None, helptext) + text = (None, formhelptext) return text, options elif verifyfunc(caller, newvalue) is not True: newvalue = verifyfunc(caller, newvalue) @@ -372,7 +388,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): formdata.update({matched_field:newvalue}) caller.ndb._menutree.formdata = formdata caller.msg("Field '%s' set to: %s" % (matched_field, str(newvalue))) - text = (None, helptext) + text = (None, formhelptext) return text, options @@ -382,10 +398,10 @@ def form_template_to_dict(formtemplate): form template, as formatted above. Args: - formtemplate (list of dicts): Tempate for the form to be initialized + formtemplate (list of dicts): Tempate for the form to be initialized. Returns: - formdata (dict): Dictionary of initalized form data + formdata (dict): Dictionary of initalized form data. """ formdata = {} @@ -409,9 +425,9 @@ def display_formdata(formtemplate, formdata, formdata (dict): Form's current data Options: - pretext (str): Text to put before the form table - posttext (str): Text to put after the form table - borderstyle (str): EvTable's border style + pretext (str): Text to put before the form table. + posttext (str): Text to put after the form table. + borderstyle (str): EvTable's border style. """ formtable = evtable.EvTable(border=borderstyle, valign="t", maxwidth=80) @@ -450,8 +466,8 @@ def verify_online_player(caller, value): or else rejects their input as invalid. Args: - caller (obj): Player entering the form data - value (str): String player entered into the form, to be verified + caller (obj): Player entering the form data. + value (str): String player entered into the form, to be verified. Returns: matched_character (obj or False): dbref to a currently logged in @@ -534,8 +550,8 @@ def sendmessage(obj, text): Callback to send a message to a player. Args: - obj (obj): Player to message - text (str): Message + obj (obj): Player to message. + text (str): Message. """ obj.msg(text) @@ -544,15 +560,15 @@ def init_delayed_message(caller, formdata): Initializes a delayed message, using data from the example form. Args: - caller (obj): Character submitting the message - formdata (dict): Data from submitted form + caller (obj): Character submitting the message. + formdata (dict): Data from submitted form. """ # Retrieve data from the filled out form. # We stored the character to message as an object ref using a verifyfunc # So we don't have to do any more searching or matching here! player_to_message = formdata["Character"] message_delay = formdata["Delay"] - message = ("Message from %s: " % caller) + formdata["Message"] + message = ("Message from %s: " % caller) + str(formdata["Message"]) caller.msg("Message sent to %s!" % player_to_message) # Make a deferred call to 'sendmessage' above. From 6490267701b4b8852a694212b2abc5f2597c3911 Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Wed, 13 Jun 2018 19:03:47 -0700 Subject: [PATCH 14/17] Removing debug stuff --- evennia/contrib/fieldfill.py | 1 - 1 file changed, 1 deletion(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index 0dea4448f9..09343dbf6f 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -262,7 +262,6 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Test for 'look' command if raw_string.lower().strip() == "look": - caller.msg(syntax_err) return text, options # Test for 'clear' command From 5155f543d2d56b1d144fccb2c4a48a59e5c6b356 Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Wed, 13 Jun 2018 19:12:02 -0700 Subject: [PATCH 15/17] 'look' works proper now! --- evennia/contrib/fieldfill.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index 09343dbf6f..c019f6db9a 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -211,7 +211,7 @@ def init_fill_field(formtemplate, caller, formcallback, pretext="", posttext="", } # Initialize menu of selections - FieldEvMenu(caller, "evennia.contrib.fieldfill", startnode="menunode_fieldfill", **kwargs) + FieldEvMenu(caller, "evennia.contrib.fieldfill", startnode="menunode_fieldfill", auto_look=False, **kwargs) def menunode_fieldfill(caller, raw_string, **kwargs): @@ -261,7 +261,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): return None, None # Test for 'look' command - if raw_string.lower().strip() == "look": + if raw_string.lower().strip() == "look" or raw_string.lower().strip() == "l": return text, options # Test for 'clear' command From c678db3bc90c7b37bf0d35029340d61fcde5a0d3 Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Thu, 14 Jun 2018 13:40:53 -0700 Subject: [PATCH 16/17] Support for persistent EvMenu added --- evennia/contrib/fieldfill.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index c019f6db9a..2233b66414 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -159,7 +159,7 @@ class FieldEvMenu(evmenu.EvMenu): def init_fill_field(formtemplate, caller, formcallback, pretext="", posttext="", submitcmd="submit", borderstyle="cells", formhelptext=None, - initial_formdata=None): + persistent=False, initial_formdata=None): """ Initializes a menu presenting a player with a fillable form - once the form is submitted, the data will be passed as a dictionary to your chosen @@ -176,6 +176,7 @@ def init_fill_field(formtemplate, caller, formcallback, pretext="", posttext="", submitcmd (str): Command used to submit the form. borderstyle (str): Form's EvTable border style. formhelptext (str): Help text for the form menu (or default is provided). + persistent (bool): Whether to make the EvMenu persistent across reboots. initial_formdata (dict): Initial data for the form - a blank form with defaults specified in the template will be generated otherwise. In the case of a form used to edit properties on an object or a @@ -211,7 +212,8 @@ def init_fill_field(formtemplate, caller, formcallback, pretext="", posttext="", } # Initialize menu of selections - FieldEvMenu(caller, "evennia.contrib.fieldfill", startnode="menunode_fieldfill", auto_look=False, **kwargs) + FieldEvMenu(caller, "evennia.contrib.fieldfill", startnode="menunode_fieldfill", + auto_look=False, persistent=persistent, **kwargs) def menunode_fieldfill(caller, raw_string, **kwargs): @@ -221,15 +223,25 @@ def menunode_fieldfill(caller, raw_string, **kwargs): submitted, the form data is passed to a callback as a dictionary. """ - # Retrieve menu info - formdata = caller.ndb._menutree.formdata - formtemplate = caller.ndb._menutree.formtemplate - formcallback = caller.ndb._menutree.formcallback - pretext = caller.ndb._menutree.pretext - posttext = caller.ndb._menutree.posttext - submitcmd = caller.ndb._menutree.submitcmd - borderstyle = caller.ndb._menutree.borderstyle - formhelptext = caller.ndb._menutree.formhelptext + # Retrieve menu info - taken from ndb if not persistent or db if persistent + if not caller.db._menutree: + formdata = caller.ndb._menutree.formdata + formtemplate = caller.ndb._menutree.formtemplate + formcallback = caller.ndb._menutree.formcallback + pretext = caller.ndb._menutree.pretext + posttext = caller.ndb._menutree.posttext + submitcmd = caller.ndb._menutree.submitcmd + borderstyle = caller.ndb._menutree.borderstyle + formhelptext = caller.ndb._menutree.formhelptext + else: + formdata = caller.db._menutree.formdata + formtemplate = caller.db._menutree.formtemplate + formcallback = caller.db._menutree.formcallback + pretext = caller.db._menutree.pretext + posttext = caller.db._menutree.posttext + submitcmd = caller.db._menutree.submitcmd + borderstyle = caller.db._menutree.borderstyle + formhelptext = caller.db._menutree.formhelptext # Syntax error syntax_err = "Syntax: = |/Or: clear , help, look, quit|/'%s' to submit form" % submitcmd From 7baa87854b54bd473bd7fa3a187335048a36a30f Mon Sep 17 00:00:00 2001 From: BattleJenkins Date: Thu, 14 Jun 2018 15:33:48 -0700 Subject: [PATCH 17/17] Add boolean field support --- evennia/contrib/fieldfill.py | 103 ++++++++++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 15 deletions(-) diff --git a/evennia/contrib/fieldfill.py b/evennia/contrib/fieldfill.py index 2233b66414..2595f4eaf9 100644 --- a/evennia/contrib/fieldfill.py +++ b/evennia/contrib/fieldfill.py @@ -25,7 +25,7 @@ a brief character profile: PROFILE_TEMPLATE = [ {"fieldname":"Name", "fieldtype":"text"}, {"fieldname":"Age", "fieldtype":"number"}, - {"fieldname":"History", "fieldtype":"text"} + {"fieldname":"History", "fieldtype":"text"}, ] This will present the player with an EvMenu showing this basic form: @@ -102,7 +102,10 @@ maximum character length for the player's input. There are lots of ways to present the form to the player - fields can have default values or show a custom message in place of a blank value, and player input can be -verified by a custom function, allowing for a great deal of flexibility. +verified by a custom function, allowing for a great deal of flexibility. There +is also an option for 'bool' fields, which accept only a True / False input and +can be customized to represent the choice to the player however you like (E.G. +Yes/No, On/Off, Enabled/Disabled, etc.) This module contains a simple example form that demonstrates all of the included functionality - a command that allows a player to compose a message to another @@ -113,11 +116,15 @@ CmdTestMenu to your default character's command set. FIELD TEMPLATE KEYS: Required: fieldname (str): Name of the field, as presented to the player. - fieldtype (str):Type of value required, either 'text' or 'number'. + fieldtype (str): Type of value required: 'text', 'number', or 'bool'. Optional: max (int): Maximum character length (if text) or value (if number). min (int): Minimum charater length (if text) or value (if number). + truestr (str): String for a 'True' value in a bool field. + (E.G. 'On', 'Enabled', 'Yes') + falsestr (str): String for a 'False' value in a bool field. + (E.G. 'Off', 'Disabled', 'No') default (str): Initial value (blank if not given). blankmsg (str): Message to show in place of value when field is blank. cantclear (bool): Field can't be cleared if True. @@ -128,7 +135,8 @@ Optional: the input is rejected. Any other value returned will act as the field's new value, replacing the player's input. This allows for values that aren't strings or integers (such as - object dbrefs). + object dbrefs). For boolean fields, return '0' or '1' to set + the field to False or True. """ from evennia.utils import evmenu, evtable, delay, list_to_string @@ -264,12 +272,16 @@ def menunode_fieldfill(caller, raw_string, **kwargs): # Add to blank and required fields blank_and_required.append(field["fieldname"]) if len(blank_and_required) > 0: + # List the required fields left empty to the player caller.msg("The following blank fields require a value: %s" % list_to_string(blank_and_required)) text = (None, formhelptext) return text, options # If everything checks out, pass form data to the callback and end the menu! - formcallback(caller, formdata) + try: + formcallback(caller, formdata) + except Exception: + log_trace("Error in fillable form callback.") return None, None # Test for 'look' command @@ -318,7 +330,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): fieldname = entry[0].strip() newvalue = entry[1].strip() - # Syntax error of field name is too short or blank + # Syntax error if field name is too short or blank if len(fieldname) < 1: caller.msg(syntax_err) text = (None, formhelptext) @@ -341,6 +353,8 @@ def menunode_fieldfill(caller, raw_string, **kwargs): fieldtype = None max_value = None min_value = None + truestr = "True" + falsestr = "False" verifyfunc = None for field in formtemplate: if field["fieldname"] == matched_field: @@ -349,10 +363,14 @@ def menunode_fieldfill(caller, raw_string, **kwargs): max_value = field["max"] if "min" in field.keys(): min_value = field["min"] + if "truestr" in field.keys(): + truestr = field["truestr"] + if "falsestr" in field.keys(): + falsestr = field["falsestr"] if "verifyfunc" in field.keys(): verifyfunc = field["verifyfunc"] - # Field type text update + # Field type text verification if fieldtype == "text": # Test for max/min if max_value != None: @@ -366,7 +384,7 @@ def menunode_fieldfill(caller, raw_string, **kwargs): text = (None, formhelptext) return text, options - # Field type number update + # Field type number verification if fieldtype == "number": try: newvalue = int(newvalue) @@ -385,6 +403,17 @@ def menunode_fieldfill(caller, raw_string, **kwargs): caller.msg("Field '%s' reqiures a minimum value of %i." % (matched_field, min_value)) text = (None, formhelptext) return text, options + + # Field type bool verification + if fieldtype == "bool": + if newvalue.lower() != truestr.lower() and newvalue.lower() != falsestr.lower(): + caller.msg("Please enter '%s' or '%s' for field '%s'." % (truestr, falsestr, matched_field)) + text = (None, formhelptext) + return text, options + if newvalue.lower() == truestr.lower(): + newvalue = True + elif newvalue.lower() == falsestr.lower(): + newvalue = False # Call verify function if present if verifyfunc: @@ -394,11 +423,26 @@ def menunode_fieldfill(caller, raw_string, **kwargs): return text, options elif verifyfunc(caller, newvalue) is not True: newvalue = verifyfunc(caller, newvalue) + # Set '0' or '1' to True or False if the field type is bool + if fieldtype == "bool": + if newvalue == 0: + newvalue = False + elif newvalue == 1: + newvalue = True # If everything checks out, update form!! formdata.update({matched_field:newvalue}) caller.ndb._menutree.formdata = formdata - caller.msg("Field '%s' set to: %s" % (matched_field, str(newvalue))) + + # Account for truestr and falsestr when updating a boolean form + announced_newvalue = newvalue + if newvalue is True: + announced_newvalue = truestr + elif newvalue is False: + announced_newvalue = falsestr + + # Announce the new value to the player + caller.msg("Field '%s' set to: %s" % (matched_field, str(announced_newvalue))) text = (None, formhelptext) return text, options @@ -459,11 +503,15 @@ def display_formdata(formtemplate, formdata, new_fieldvalue = "|x" + str(field["blankmsg"]) + "|n" elif new_fieldvalue == None: new_fieldvalue = " " + # Replace True and False values with truestr and falsestr from template + if formdata[field["fieldname"]] is True and "truestr" in field: + new_fieldvalue = field["truestr"] + elif formdata[field["fieldname"]] is False and "falsestr" in field: + new_fieldvalue = field["falsestr"] # Add name and value to table formtable.add_row(new_fieldname, new_fieldvalue) formtable.reformat_column(0, align="r", width=field_name_width) - # formtable.reformat_column(1, pad_left=0) return pretext + "|/" + str(formtable) + "|/" + posttext @@ -518,10 +566,32 @@ def verify_online_player(caller, value): # Form template for the example 'delayed message' form SAMPLE_FORM = [ -{"fieldname":"Character", "fieldtype":"text", "max":30, "blankmsg":"(Name of an online player)", - "required":True, "verifyfunc":verify_online_player}, -{"fieldname":"Delay", "fieldtype":"number", "min":3, "max":30, "default":10, "cantclear":True}, -{"fieldname":"Message", "fieldtype":"text", "min":3, "max":200, "blankmsg":"(Message up to 200 characters)"} +{"fieldname":"Character", + "fieldtype":"text", + "max":30, + "blankmsg":"(Name of an online player)", + "required":True, + "verifyfunc":verify_online_player + }, +{"fieldname":"Delay", + "fieldtype":"number", + "min":3, + "max":30, + "default":10, + "cantclear":True + }, +{"fieldname":"Message", + "fieldtype":"text", + "min":3, + "max":200, + "blankmsg":"(Message up to 200 characters)" + }, +{"fieldname":"Anonymous", + "fieldtype":"bool", + "truestr":"Yes", + "falsestr":"No", + "default":False + } ] class CmdTestMenu(Command): @@ -579,7 +649,10 @@ def init_delayed_message(caller, formdata): # So we don't have to do any more searching or matching here! player_to_message = formdata["Character"] message_delay = formdata["Delay"] - message = ("Message from %s: " % caller) + str(formdata["Message"]) + sender = str(caller) + if formdata["Anonymous"] is True: + sender = "anonymous" + message = ("Message from %s: " % sender) + str(formdata["Message"]) caller.msg("Message sent to %s!" % player_to_message) # Make a deferred call to 'sendmessage' above.