Imported from archive.
authorJeremy Stanley <fungi@yuggoth.org>
Thu, 25 Aug 2005 02:25:34 +0000 (02:25 +0000)
committerJeremy Stanley <fungi@yuggoth.org>
Thu, 25 Aug 2005 02:25:34 +0000 (02:25 +0000)
* lib/menus/active: Merged into lib/menus/miscellaneous and removed.

* lib/menus/miscellaneous (choose_gender, choose_name)
(main_utility): Added menus to enter/exit the game, select gender
and pick a randomly-generated avatar name from a list.

* lib/muff/muffcmds.py: Changed user state names to employ
underscores instead of spaces.
(command_show): New admin command to ease debugging of data
relationships.
(generic_menu_handler): New function abstract out common routines
from typical menu state handlers.
(handle_user_input): For aesthetics, user input is now followed by a
blank line of output if we expect the client isn't echoing.

* lib/muff/muffmain.py (main): The main loop is now followed by a
muffiniv.universe.save during shutdown, in case any changes have
occurred since the last save.

* lib/muff/muffmenu.py
(get_menu): Refactored to be more modular, and broke out a lot of
its pieces into additional functions.
(get_choice_action, get_choice_branch, get_default_action)
(get_default_branch, get_default_menu_choice)
(get_formatted_default_menu_choice, get_formatted_menu_choices)
(get_menu_actions, get_menu_branches, get_menu_choices)
(get_menu_description, get_menu_prompt): New functions abstracted
out of the previously spaghetti-like get_menu function.
(get_echo_message, get_echo_sequence, menu_echo_on): New functions
to take care of enabling and disabling echo for password prompts.

* lib/muff/muffmisc.py (replace_macros): Implemented gender-specific
pronoun replacement macros, to make English-language messages less
awkward.

* lib/muff/muffuniv.py: New module implementing DataFile, Element
and Universe classes, for a more object-oriented approach.

* muff: Renamed to mud.py.

* muff.conf: Renamed to mudpy.conf.

20 files changed:
lib/commands/active
lib/index [new file with mode: 0644]
lib/menus/account_creation
lib/menus/active [deleted file]
lib/menus/index
lib/menus/login
lib/menus/miscellaneous
lib/muff/__init__.py
lib/muff/muffcmds.py
lib/muff/muffconf.py
lib/muff/muffmain.py
lib/muff/muffmenu.py
lib/muff/muffmisc.py
lib/muff/muffuniv.py [new file with mode: 0644]
lib/muff/muffuser.py
lib/muff/muffvars.py
lib/universe/index [new file with mode: 0644]
lib/universe/rooms [new file with mode: 0644]
mud.py [moved from muff with 97% similarity]
mudpy.conf [moved from muff.conf with 97% similarity]

index c977538..0b1ae5b 100644 (file)
@@ -18,6 +18,10 @@ help = This will reload all python code modules, reload configuration files and
 description = State something out loud.
 help = This allows you to speak to other characters within the same room. If you end your sentence with specific punctuation, the aparent speech action (ask, exclaim, et cetera) will be adapted accordingly. It will also add punctuation and capitalize your message where needed.
 
+[show]
+description = Show program data.
+help = For now, this is used to show things like "universe" and "avatars".
+
 [time]
 description = Show the current world time in elapsed increments.
 help = This will show the current world time in elapsed increments.
diff --git a/lib/index b/lib/index
new file mode 100644 (file)
index 0000000..e69de29
index dc7f032..6402fab 100644 (file)
@@ -1,4 +1,4 @@
-[checking new account name]
+[checking_new_account_name]
 description = There is no existing account for "$(account)" (note that an account name is not the same as a character name). Would you like to create a new account by this name, go back and enter a different name or disconnect now?
 choice_d = disconnect now
 choice_g = go back
@@ -6,13 +6,13 @@ choice_n = new account
 prompt = Enter your choice:
 default = d
 
-[entering new password]
+[entering_new_password]
 prompt = Enter a new password for "$(account)":
 echo = off
 error_weak = That is a weak password... Try something at least 7 characters long with a combination of mixed-case letters, numbers and punctuation/spaces.
 error_differs = The two passwords did not match. Try again...
 
-[verifying new password]
+[verifying_new_password]
 prompt = Enter the same new password again:
 echo = off
 
diff --git a/lib/menus/active b/lib/menus/active
deleted file mode 100644 (file)
index 333dd9f..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-[active]
-prompt = >
-
index 9b5802e..d5fb932 100644 (file)
@@ -1,3 +1,3 @@
 [index]
-files = account_creation active login miscellaneous
+files = account_creation login miscellaneous
 
index cf2659c..831328b 100644 (file)
@@ -1,12 +1,12 @@
-[entering account name]
+[entering_account_name]
 description = Welcome to the mudpy example...
 prompt = Identify yourself:
 
-[checking password]
+[checking_password]
 prompt = Password:
 echo = off
 error_incorrect = Incorrect password, please try again...
 
-[disconnecting duplicates]
+[disconnecting_duplicates]
 prompt = $(red)Closing your previous connection...$(nrm)$(eol)
 
index f93a6c3..ef18de5 100644 (file)
@@ -1,3 +1,44 @@
+[active]
+prompt = >
+
 [disconnecting]
 description = $(red)Disconnecting...$(nrm)
 
+[main_utility]
+description = From here you can activate, create and delete avatars. An avatar is your persona in the world of Example.
+choice_a = activate an existing avatar
+#branch_a = activate_avatar
+branch_a = active
+choice_c = create a new avatar
+action_c = user.new_avatar()
+branch_c = choose_gender
+choice_d = delete an unwanted avatar
+branch_d = delete_avatar
+choice_l = leave example for now
+branch_l = disconnecting
+choice_p = permanently remove your account
+branch_p = delete_account
+prompt = What would you like to do?
+error_no_avatars = You don't have any avatars yet. An avatar is your persona in the world of Example. It is recommended that you create one now.
+
+[choose_gender]
+description = First, your new avatar needs a gender. In the world of Example, all avatars are either male or female.
+choice_f = female
+choice_m = male
+action = user.avatar.rec("gender", user.menu_choices[choice])
+branch = choose_name
+prompt = Pick a gender for your new avatar:
+
+[choose_name]
+description = Your new avatar needs a name. This will be the name with which $(tpsp) grew up, and will initially be the name by which $(tpsp) is known in the world of Example. There are ways for your new avatar to make a name for $(tpop)self over time, so $(tpsp) won't be stuck going by such an unremarkable name forever.
+create_1 = muffmisc.random_name()
+create_2 = muffmisc.random_name()
+create_3 = muffmisc.random_name()
+create_4 = muffmisc.random_name()
+create_5 = muffmisc.random_name()
+create_6 = muffmisc.random_name()
+create_7 = muffmisc.random_name()
+action = user.avatar.rec("name", user.menu_choices[choice])
+branch = active
+prompt = Choose a name for $(tpop):
+
index 318e7a8..c4c88f2 100644 (file)
@@ -5,5 +5,5 @@
 
 # these are all the modules included in the muff package; if you create
 # another, be sure to add it to this list
-__all__ = [ "muffcmds", "muffconf", "muffmain", "muffmenu", "muffmisc", "muffsock", "muffuser", "muffvars" ]
+__all__ = [ "muffcmds", "muffconf", "muffmain", "muffmenu", "muffmisc", "muffsock", "muffuniv", "muffuser", "muffvars" ]
 
index 6ade3b1..c640c96 100644 (file)
@@ -53,11 +53,32 @@ def handle_user_input(user):
        """The main handler, branches to a state-specific handler."""
 
        # check to make sure the state is expected, then call that handler
-       exec("handler_" + user.state.replace(" ", "_") + "(user)")
+       try:
+               exec("handler_" + user.state + "(user)")
+       except NameError:
+               generic_menu_handler(user)
 
        # since we got input, flag that the menu/prompt needs to be redisplayed
        user.menu_seen = False
 
+       # if the user's client echo is off, send a blank line for aesthetics
+       if not user.echoing: user.send("", "")
+
+def generic_menu_handler(user):
+       """A generic menu choice handler."""
+
+       # get a lower-case representation of the next line of input
+       choice = user.input_queue.pop(0)
+       if choice: choice = choice.lower()
+
+       # run any script related to this choice
+       exec(muffmenu.get_choice_action(user, choice))
+
+       # move on to the next state or return an error
+       new_state = muffmenu.get_choice_branch(user, choice)
+       if new_state: user.state = new_state
+       else: user.error = "default"
+
 def handler_entering_account_name(user):
        """Handle the login account name."""
 
@@ -72,7 +93,7 @@ def handler_entering_account_name(user):
 
                # if we have a password hash, time to request a password
                if user.get_passhash():
-                       user.state = "checking password"
+                       user.state = "checking_password"
 
                # otherwise, this could be a brand new user
                else:
@@ -80,7 +101,7 @@ def handler_entering_account_name(user):
                        user.proposed_name = None
                        user.load()
                        muffmisc.log("New user: " + user.name)
-                       user.state = "checking new account name"
+                       user.state = "checking_new_account_name"
 
        # if the user entered nothing for a name, then buhbye
        else:
@@ -96,13 +117,12 @@ def handler_checking_password(user):
        if md5.new(user.proposed_name + input_data).hexdigest() == user.passhash:
 
                # if so, set the username and load from cold storage
-               # TODO: branch to character creation and selection menus
                user.name = user.proposed_name
                del(user.proposed_name)
                if not user.replace_old_connections():
                        user.load()
                        user.authenticate()
-                       user.state = "active"
+                       user.state = "main_utility"
 
        # if at first your hashes don't match, try, try again
        elif user.password_tries < muffconf.getint("general", "password_tries"):
@@ -114,14 +134,6 @@ def handler_checking_password(user):
                user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
                user.state = "disconnecting"
 
-def handler_disconnecting(user):
-       """Waiting for the user's connection to close."""
-       pass
-
-def handler_disconnecting_duplicates(user):
-       """Waiting for duplicate connections to close."""
-       pass
-
 def handler_checking_new_account_name(user):
        """Handle input for the new user menu."""
 
@@ -134,7 +146,7 @@ def handler_checking_new_account_name(user):
 
        # if there's no input, use the default
        else:
-               choice = muffmenu.get_default(user)
+               choice = muffmenu.get_default_menu_choice(user.state)
 
        # user selected to disconnect
        if choice == "d":
@@ -142,11 +154,11 @@ def handler_checking_new_account_name(user):
 
        # go back to the login screen
        elif choice == "g":
-               user.state = "entering account name"
+               user.state = "entering_account_name"
 
        # new user, so ask for a password
        elif choice == "n":
-               user.state = "entering new password"
+               user.state = "entering_new_password"
 
        # user entered a non-existent option
        else:
@@ -164,7 +176,7 @@ def handler_entering_new_password(user):
 
                # hash and store it, then move on to verification
                user.passhash = md5.new(user.name + input_data).hexdigest()
-               user.state = "verifying new password"
+               user.state = "verifying_new_password"
 
        # the password was weak, try again if you haven't tried too many times
        elif user.password_tries < muffconf.getint("general", "password_tries"):
@@ -188,15 +200,14 @@ def handler_verifying_new_password(user):
                user.save()
 
                # the hashes matched, so go active
-               # TODO: branch to character creation and selection menus
-               if not user.replace_old_connections(): user.state = "active"
+               if not user.replace_old_connections(): user.state = "main_utility"
 
        # go back to entering the new password as long as you haven't tried
        # too many times
        elif user.password_tries < muffconf.getint("general", "password_tries"):
                user.password_tries += 1
                user.error = "differs"
-               user.state = "entering new password"
+               user.state = "entering_new_password"
 
        # otherwise, sayonara
        else:
@@ -357,6 +368,22 @@ def command_say(user, command="", parameters=""):
        else:
                user.send("What do you want to say?")
 
+def command_show(user, command="", parameters=""):
+       """Show program data."""
+       if parameters == "universe":
+               message = "These are the current elements in the universe:$(eol)"
+               keys = muffuniv.universe.contents.keys()
+               keys.sort()
+               for key in keys: message += "$(eol)   $(grn)" + key + "$(nrm)"
+       elif parameters == "avatars":
+               message = "These are the avatars managed by your account:$(eol)"
+               avatars = user.list_avatar_names()
+               avatars.sort()
+               for avatar in avatars: message += "$(eol)   $(grn)" + avatar + "$(nrm)"
+       elif parameters: message = "I don't know what \"" + parameters + "\" is."
+       else: message = "What do you want to show?"
+       user.send(message)
+
 def command_error(user, command="", parameters=""):
        """Generic error for an unrecognized command word."""
 
index 7992f12..8113c64 100644 (file)
@@ -18,7 +18,7 @@ for module in muff.__all__:
 config_dirs = [".", "./etc", "/usr/local/muff", "/usr/local/muff/etc", "/etc/muff", "/etc" ]
 
 # name of the config file
-config_name = "muff.conf"
+config_name = "mudpy.conf"
 
 # find the config file
 for each_dir in config_dirs:
index 89725bc..4ddab12 100644 (file)
@@ -40,8 +40,9 @@ def main():
                # do what needs to be done on each pulse
                muffmisc.on_pulse()
 
-       # the loop has terminated, so save persistent variables
+       # the loop has terminated, so save persistent data
        muffvars.save()
+       muffuniv.universe.save()
 
        # log a final message
        muffmisc.log("Shutting down now.")
index cdf0d5c..daf6512 100644 (file)
@@ -6,12 +6,6 @@
 # muff uses menu data stored in ini-style files supported by ConfigParser
 import ConfigParser
 
-# os.listdir is needed to get a file listing from a config directory
-import os
-
-# re.match is used to find menu options based on the choice_ prefix
-import re
-
 # hack to load all modules in the muff package
 import muff
 for module in muff.__all__:
@@ -37,122 +31,170 @@ for each_file in menu_files_index.get("index", "files").split():
 menu_data = ConfigParser.SafeConfigParser()
 menu_data.read(menu_files)
 
-def get_default(user):
-       """Return the default choice for a menu."""
-       return menu_data.get(user.state, "default")
-
-def get_menu(user):
+def get_menu(state, error=None, echoing=True, choices={}):
        """Show the correct menu text to a user."""
 
-       # the menu hasn't been shown yet since the user's last input
-       if not user.menu_seen:
-
-               # echo is currently on for the user, so don't add an extra eol
-               if user.echoing:
-                       spacer = ""
+       # begin with a telnet echo command sequence if needed
+       message = get_echo_sequence(state, echoing)
 
-               # echo is currently off for the user, so add an extra eol
-               else:
-                       spacer = "$(eol)"
+       # get the description or error text
+       message += get_menu_description(state, error)
 
-               # if the user has echo on and the menu specifies it should be
-               # turned off, send: iac + will + echo + null
-               try:
-                       if menu_data.get(user.state, "echo") == "off" and user.echoing:
-                               echo = chr(255) + chr(251) + chr(1) + chr(0)
-                               user.echoing = False
-                       else:
-                               echo = ""
-
-               # if echo is not set to off in the menu and the user curently
-               # has echo off, send: iac + wont + echo + null
-               except:
-                       if not user.echoing:
-                               echo = chr(255) + chr(252) + chr(1) + chr(0)
-                               user.echoing = True
-                       else:
-                               echo = ""
-
-               # and error condition was raised by the handler
-               if user.error:
+       # get menu choices for the current state
+       message += get_formatted_menu_choices(state, choices)
 
-                       # try to get an error message matching the condition
-                       # and current state
-                       try:
-                               description = "$(red)" + menu_data.get(user.state, "error_" + user.error) + "$(nrm)$(eol)$(eol)"
+       # try to get a prompt, if it was defined
+       message += get_menu_prompt(state)
 
-                       # otherwise, use a generic one
-                       except:
-                               description = "$(red)That is not a valid choice...$(nrm)$(eol)$(eol)"
+       # throw in the default choice, if it exists
+       message += get_formatted_default_menu_choice(state)
 
-                       # now clear the error condition
-                       user.error = ""
+       # display a message indicating if echo is off
+       message += get_echo_message(state)
 
-               # there was no error condition raised by the handler
-               else:
+       # return the assembly of various strings defined above
+       return message
 
-                       # try to get a menu description for the current state
-                       try:
-                               description = menu_data.get(user.state, "description") + "$(eol)$(eol)"
+def menu_echo_on(state):
+       """True if echo is on, false if it is off."""
+       try:
+               return menu_data.getboolean(state, "echo")
+       except:
+               return True
 
-                       # otherwise, leave it blank
-                       except:
-                               description = ""
-
-               # try to get menu choices for the current state
-               try:
+def get_echo_sequence(state, echoing):
+       """Build the appropriate IAC ECHO sequence as needed."""
 
-                       # build a dict of choice:meaning
-                       choices = {}
-                       for option in menu_data.options(user.state):
-                               if re.match("choice_", option):
-                                       choices[option.split("_")[1]] = menu_data.get(user.state, option)
+       # if the user has echo on and the menu specifies it should be turned
+       # off, send: iac + will + echo + null
+       if echoing and not menu_echo_on(state): return chr(255) + chr(251) + chr(1) + chr(0)
 
-                       # make a sorted list of choices
-                       choice_keys = choices.keys()
-                       choice_keys.sort()
+       # if echo is not set to off in the menu and the user curently has echo
+       # off, send: iac + wont + echo + null
+       elif not echoing and menu_echo_on(state): return chr(255) + chr(252) + chr(1) + chr(0)
 
-                       # concatenate them all into a list for display
-                       choicestring = ""
-                       for choice in choice_keys:
-                               choicestring += "   [$(red)" + choice + "$(nrm)]  " + choices[choice] + "$(eol)"
+       # default is not to send an echo control sequence at all
+       else: return ""
 
-                       # throw in an additional blank line after the choices,
-                       # if there were any
-                       if choicestring:
-                               choicestring += "$(eol)"
+def get_echo_message(state):
+       """Return a message indicating that echo is off."""
+       if menu_echo_on(state): return ""
+       else: return "(won't echo) "
 
-               # there were no choices, so leave them blank
-               except:
-                       choicestring = ""
-
-               # try to get a prompt, if it was defined
+def get_default_menu_choice(state):
+       """Return the default choice for a menu."""
+       try:
+               return menu_data.get(state, "default")
+       except:
+               return ""
+def get_formatted_default_menu_choice(state):
+       """Default menu choice foratted for inclusion in a prompt string."""
+       default = get_default_menu_choice(state)
+       if default: return "[$(red)" + default + "$(nrm)] "
+       else: return ""
+
+def get_menu_description(state, error):
+       """Get the description or error text."""
+
+       # an error condition was raised by the handler
+       if error:
+
+               # try to get an error message matching the condition
+               # and current state
                try:
-                       prompt = menu_data.get(user.state, "prompt") + " "
+                       return "$(red)" + menu_data.get(state, "error_" + error) + "$(nrm)$(eol)$(eol)"
 
-               # otherwise, leave it blank
+               # otherwise, use a generic one
                except:
-                       prompt = ""
+                       return "$(red)That is not a valid choice...$(nrm)$(eol)$(eol)"
+
+       # there was no error condition
+       else:
 
-               # throw in the default choice, if it exists
+               # try to get a menu description for the current state
                try:
-                       default = "[$(red)" + menu_data.get(user.state, "default") + "$(nrm)] "
+                       return menu_data.get(state, "description") + "$(eol)$(eol)"
 
                # otherwise, leave it blank
                except:
-                       default = ""
-
-               # echo is on, so don't display a message about it
-               if user.echoing:
-                       echoing = ""
-
-               # echo is off, so let the user know
-               else:
-                       echoing = "(won't echo) "
-
-               # assemble and send the various strings defined above
-               user.send(echo + spacer + description + choicestring + prompt + default + echoing, "")
-
-               # flag that the menu has now been displayed
-               user.menu_seen = True
+                       return ""
+
+def get_menu_prompt(state):
+       """Try to get a prompt, if it was defined."""
+       try:
+               return menu_data.get(state, "prompt") + " "
+       except:
+               return ""
+
+def get_menu_choices(user):
+       """Return a dict of choice:meaning."""
+       choices = {}
+       for option in menu_data.options(user.state):
+               if option.startswith("choice_"):
+                       choices[option.split("_", 2)[1]] = menu_data.get(user.state, option)
+               elif option.startswith("create_"):
+                       choices[option.split("_", 2)[1]] = eval(menu_data.get(user.state, option))
+       return choices
+
+def get_formatted_menu_choices(state, choices):
+       """Returns a formatted string of menu choices."""
+       choice_output = ""
+       choice_keys = choices.keys()
+       choice_keys.sort()
+       for choice in choice_keys:
+               choice_output += "   [$(red)" + choice + "$(nrm)]  " + choices[choice] + "$(eol)"
+       if choice_output: choice_output += "$(eol)"
+       return choice_output
+
+def get_menu_branches(state):
+       """Return a dict of choice:branch."""
+       branches = {}
+       try:
+               for option in menu_data.options(state):
+                       if option.startswith("branch_"):
+                               branches[option.split("_", 2)[1]] = menu_data.get(state, option)
+       except:
+               pass
+       return branches
+
+def get_default_branch(state):
+       """Return the default branch."""
+       try:
+               return menu_data.get(state, "branch")
+       except:
+               return ""
+
+def get_choice_branch(user, choice):
+       """Returns the new state matching the given choice."""
+       branches = get_menu_branches(user.state)
+       if not choice: choice = get_default_menu_choice(user.state)
+       if choice in branches.keys(): return branches[choice]
+       elif choice in user.menu_choices.keys(): return get_default_branch(user.state)
+       else: return ""
+
+def get_menu_actions(state):
+       """Return a dict of choice:branch."""
+       actions = {}
+       try:
+               for option in menu_data.options(state):
+                       if option.startswith("action_"):
+                               actions[option.split("_", 2)[1]] = menu_data.get(state, option)
+       except:
+               pass
+       return actions
+
+def get_default_action(state):
+       """Return the default action."""
+       try:
+               return menu_data.get(state, "action")
+       except:
+               return ""
+
+def get_choice_action(user, choice):
+       """Run any indicated script for the given choice."""
+       actions = get_menu_actions(user.state)
+       if not choice: choice = get_default_menu_choice(user.state)
+       if choice in actions.keys(): return actions[choice]
+       elif choice in user.menu_choices.keys(): return get_default_action(user.state)
+       else: return ""
 
index d2848b2..749af91 100644 (file)
@@ -3,6 +3,8 @@
 # Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
 # Licensed per terms in the LICENSE file distributed with this software.
 
+import ConfigParser
+
 # used by several functions for random calls
 import random
 
@@ -147,7 +149,14 @@ def repr_long(value):
        else: return string_value
 
 def getlong(config, section, option):
-       return int(config.get(section, option).strip("L"))
+       try:
+               return int(config.get(section, option).strip("L"))
+       except ConfigParser.NoSectionError:
+               config.add_section(section)
+               return getlong(config, section, option)
+       except ConfigParser.NoOptionError:
+               setlong(config, section, option, 0)
+               return getlong(config, section, option)
 
 def setlong(config, section, option, value):
        return config.set(section, option, repr_long(value))
@@ -165,6 +174,33 @@ def replace_macros(user, text, is_input=False):
                elif macro == "$(account)":
                        text = string.replace(text, macro, user.name)
 
+               # third person subjective pronoun
+               elif macro == "$(tpsp)":
+                       if user.avatar.get("gender") == "male":
+                               text = string.replace(text, macro, "he")
+                       elif user.avatar.get("gender") == "female":
+                               text = string.replace(text, macro, "she")
+                       else:
+                               text = string.replace(text, macro, "it")
+
+               # third person objective pronoun
+               elif macro == "$(tpop)":
+                       if user.avatar.get("gender") == "male":
+                               text = string.replace(text, macro, "him")
+                       elif user.avatar.get("gender") == "female":
+                               text = string.replace(text, macro, "her")
+                       else:
+                               text = string.replace(text, macro, "it")
+
+               # third person possessive pronoun
+               elif macro == "$(tppp)":
+                       if user.avatar.get("gender") == "male":
+                               text = string.replace(text, macro, "his")
+                       elif user.avatar.get("gender") == "female":
+                               text = string.replace(text, macro, "hers")
+                       else:
+                               text = string.replace(text, macro, "its")
+
                # if we get here, log and replace it with null
                else:
                        text = string.replace(text, macro, "")
@@ -203,6 +239,7 @@ def on_pulse():
        if check_time("frequency_save"):
                muffvars.save()
                for user in muffvars.userlist: user.save()
+               muffuniv.universe.save()
 
        # pause for a configurable amount of time (decimal seconds)
        time.sleep(muffconf.getfloat("time", "increment"))
diff --git a/lib/muff/muffuniv.py b/lib/muff/muffuniv.py
new file mode 100644 (file)
index 0000000..91a4ca5
--- /dev/null
@@ -0,0 +1,91 @@
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
+
+# persistent variables are stored in ini-style files supported by ConfigParser
+import ConfigParser
+
+# need to know the directory separator
+import os
+
+# hack to load all modules in the muff package
+import muff
+for module in muff.__all__:
+       exec("import " + module)
+
+class Element:
+       """An element of the universe."""
+       def __init__(self, key, origin, universe):
+               """Default values for the in-memory element variables."""
+               self.key = key
+               self.origin = origin
+               universe.contents[key] = self
+               if not universe.files[origin].data.has_section(key):
+                       universe.files[origin].data.add_section(key)
+       def facets(self):
+               """Return a list of facets for this element."""
+               return universe.files[self.origin].data.options(self.key)
+       def get(self, facet):
+               """Retrieve values."""
+               if facet in dir(self): return self.facet
+               else:
+                       try:
+                               return universe.files[self.origin].data.get(self.key, facet)
+                       except:
+                               return None
+       def set(self, facet, value):
+               """Set values."""
+               if facet in universe.files[self.origin].data.options(self.key):
+                       universe.files[self.origin].data.set(self.key, facet, value)
+               else: self.facet = value
+       def rec(self, facet, value):
+               """Add initial values to the record."""
+               universe.files[self.origin].data.set(self.key, facet, value)
+
+class DataFile:
+       """A file containing universe elements."""
+       def __init__(self, filename, universe):
+               filedir = os.sep.join(filename.split(os.sep)[:-1])
+               data = ConfigParser.SafeConfigParser()
+               data.read(filename)
+               self.filename = filename
+               self.data = data
+               universe.files[filename] = self
+               for section in data.sections():
+                       if section == "include":
+                               for option in data.options(section):
+                                       includefile = data.get(section, option)
+                                       if not includefile.startswith(os.sep):
+                                               includefile = filedir + os.sep + includefile
+                                       DataFile(includefile, universe)
+                       else:
+                               Element(section, filename, universe)
+       def save(self):
+               basedir = os.sep.join(self.filename.split(os.sep)[:-1])
+               if not os.access(basedir, os.F_OK): os.makedirs(basedir)
+               file_descriptor = file(self.filename, "w")
+               self.data.write(file_descriptor)
+               file_descriptor.flush()
+               file_descriptor.close()
+
+class Universe:
+       """The universe."""
+       def __init__(self):
+               filename = muffconf.get("files", "data")
+               if not filename.startswith(os.sep): filename = os.getcwd() + os.sep + filename
+               self.contents = {}
+               self.files = {}
+               DataFile(filename, self)
+       def save(self):
+               for key in self.files.keys(): self.files[key].save()
+
+def element_exists(key): return key in universe.contents.keys()
+
+# reload the muffconf module if the files:data setting does not exist
+try:
+       if muffconf.get("files", "data"): pass
+except AttributeError:
+       reload(muffconf)
+
+# if there is no universe, create an empty one
+if not "universe" in dir(): universe = Universe()
+
index 9a5169c..1d12be3 100644 (file)
@@ -6,7 +6,7 @@
 # user accounts are stored in ini-style files supported by ConfigParser
 import ConfigParser
 
-# test for existence of the account dir with os.listdir and os.mkdir to make it
+# os is used to test for existence of the account dir and, if necessary, make it
 import os
 
 # string.replace is used to perform substitutions for color codes and the like
@@ -45,7 +45,7 @@ class User:
                self.password_tries = 1
 
                # the current state of the user
-               self.state = "entering account name"
+               self.state = "entering_account_name"
 
                # flag to indicate whether a menu has been displayed
                self.menu_seen = False
@@ -65,6 +65,9 @@ class User:
                # flag to indicate the current echo status of the client
                self.echoing = True
 
+               # the active avatar
+               self.avatar = None
+
                # an object containing persistent account data
                self.record = ConfigParser.SafeConfigParser()
 
@@ -219,14 +222,14 @@ class User:
                        # the filename to which we'll write
                        filename = account_path + "/" + self.name.lower()
 
-                       # if the directory doesn't exist, create it
+                       # open the user account file for writing
                        try:
-                               if os.listdir(account_path): pass
-                       except:
-                               os.mkdir(account_path)
+                               record_file = file(filename, "w")
 
-                       # open the user account file for writing
-                       record_file = file(filename, "w")
+                       # if the directory doesn't exist, create it first
+                       except IOError:
+                               os.makedirs(account_path)
+                               record_file = file(filename, "w")
 
                        # dump the account data to it
                        self.record.write(record_file)
@@ -240,7 +243,17 @@ class User:
 
        def show_menu(self):
                """Send the user their current menu."""
-               self.send(muffmenu.get_menu(self))
+               if not self.menu_seen:
+                       self.menu_choices = muffmenu.get_menu_choices(self)
+                       self.send(muffmenu.get_menu(self.state, self.error, self.echoing, self.menu_choices), "")
+                       self.menu_seen = True
+                       self.error = False
+                       self.adjust_echoing()
+
+       def adjust_echoing(self):
+               """Adjust echoing to match state menu requirements."""
+               if self.echoing and not muffmenu.menu_echo_on(self.state): self.echoing = False
+               elif not self.echoing and muffmenu.menu_echo_on(self.state): self.echoing = True
 
        def remove(self):
                """Remove a user from the list of connected users."""
@@ -250,33 +263,33 @@ class User:
                """Send arbitrary text to a connected user."""
 
                # only when there is actual output
-               if output:
+               #if output:
 
-                       # start with a newline, append the message, then end
-                       # with the optional eol string passed to this function
-                       # and the ansi escape to return to normal text
-                       output = "\r\n" + output + eol + chr(27) + "[0m"
+               # start with a newline, append the message, then end
+               # with the optional eol string passed to this function
+               # and the ansi escape to return to normal text
+               output = "\r\n" + output + eol + chr(27) + "[0m"
 
-                       # find and replace macros in the output
-                       output = muffmisc.replace_macros(self, output)
+               # find and replace macros in the output
+               output = muffmisc.replace_macros(self, output)
 
-                       # wrap the text at 80 characters
-                       # TODO: prompt user for preferred wrap width
-                       output = muffmisc.wrap_ansi_text(output, 80)
+               # wrap the text at 80 characters
+               # TODO: prompt user for preferred wrap width
+               output = muffmisc.wrap_ansi_text(output, 80)
 
-                       # drop the formatted output into the output queue
-                       self.output_queue.append(output)
+               # drop the formatted output into the output queue
+               self.output_queue.append(output)
 
-                       # try to send the last item in the queue, remove it and
-                       # flag that menu display is not needed
-                       try:
-                               self.connection.send(self.output_queue[0])
-                               self.output_queue.remove(self.output_queue[0])
-                               self.menu_seen = False
+               # try to send the last item in the queue, remove it and
+               # flag that menu display is not needed
+               try:
+                       self.connection.send(self.output_queue[0])
+                       self.output_queue.remove(self.output_queue[0])
+                       self.menu_seen = False
 
-                       # but if we can't, that's okay too
-                       except:
-                               pass
+               # but if we can't, that's okay too
+               except:
+                       pass
 
        def pulse(self):
                """All the things to do to the user per increment."""
@@ -343,3 +356,31 @@ class User:
                                # put on the end of the queue
                                self.input_queue.append(line)
 
+       def new_avatar(self):
+               """Instantiate a new, unconfigured avatar for this user."""
+               try:
+                       counter = muffvars.variable_data.getint("counters", "next_actor")
+               except:
+                       muffmisc.log("get next_actor failed")
+                       counter = 1
+               while muffuniv.element_exists("actor:" + repr(counter)): counter += 1
+               muffvars.variable_data.set("counters", "next_actor", counter + 1)
+               self.avatar = muffuniv.Element("actor:" + repr(counter))
+               try:
+                       avatars = self.record.get("account", "avatars").split()
+               except:
+                       avatars = []
+               avatars.append(self.avatar.key)
+               self.record.set("account", "avatars", " ".join(avatars))
+
+       def list_avatar_names(self):
+               """A test function to list names of assigned avatars."""
+               try:
+                       avatars = self.record.get("account", "avatars").split()
+               except:
+                       avatars = []
+               avatar_names = []
+               for avatar in avatars:
+                       avatar_names.append(muffuniv.universe.contents[avatar].get("name"))
+               return avatar_names
+
index 9e6f9f1..1abfe86 100644 (file)
@@ -6,6 +6,9 @@
 # persistent variables are stored in ini-style files supported by ConfigParser
 import ConfigParser
 
+# need to be able to create the variable save file's directory
+import os
+
 # hack to load all modules in the muff package
 import muff
 for module in muff.__all__:
@@ -52,9 +55,19 @@ macros = {
        "$(red)": chr(27) + "[31m"
        }
 
-# function to save persistent variables to file
 def save():
-       file_descriptor = open(variable_file, "w")
+       """Function to save persistent variables to a file."""
+
+       # try to open the variable file
+       try:
+               file_descriptor = file(variable_file, "w")
+
+       # failing that, make the directory in which it resides first
+       except IOError:
+               os.makedirs(os.sep.join(variable_file.split(os.sep)[:-1]))
+               file_descriptor = file(variable_file, "w")
+
+       # now write the data and close out the file
        variable_data.write(file_descriptor)
        file_descriptor.flush()
        file_descriptor.close()
diff --git a/lib/universe/index b/lib/universe/index
new file mode 100644 (file)
index 0000000..afeccd6
--- /dev/null
@@ -0,0 +1,4 @@
+[include]
+actors = actors
+rooms = rooms
+
diff --git a/lib/universe/rooms b/lib/universe/rooms
new file mode 100644 (file)
index 0000000..b2d4711
--- /dev/null
@@ -0,0 +1,6 @@
+[room:0,0,0,0]
+
+[room:1,0,0,0]
+
+[room:-1,0,0,0]
+
diff --git a/muff b/mud.py
similarity index 97%
rename from muff
rename to mud.py
index 57874d4..5130834 100755 (executable)
--- a/muff
+++ b/mud.py
@@ -16,7 +16,7 @@ def get_main_loop():
        # figure out where to find our main configuration file
        config_data = ConfigParser.SafeConfigParser()
        config_dirs = [".", "./etc", "/usr/local/muff", "/usr/local/muff/etc", "/etc/muff", "/etc" ]
-       config_name = "muff.conf"
+       config_name = "mudpy.conf"
        config_files = []
        for each_dir in config_dirs:
                config_files.append(each_dir + "/" + config_name)
similarity index 97%
rename from muff.conf
rename to mudpy.conf
index 9e9331b..80cd349 100644 (file)
--- a/muff.conf
@@ -4,6 +4,7 @@ commands = ./lib/commands
 menus = ./lib/menus
 modules = ./lib
 variable = ./lib/save/variable
+data = lib/index
 
 [general]
 password_tries = 3