-[menu:choose_gender]
-choice_f = female
-prompt = Pick a gender for your new avatar:
-description = First, your new avatar needs a gender. In the world of Example, all avatars are either male or female.
-choice_m = male
-branch = choose_name
-action = user.avatar.set("gender", user.menu_choices[choice])
-
-[menu:entering_new_password]
-error_differs = The two passwords did not match. Try again...
-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.
-prompt = Enter a new password for "$(account)":
-echo = off
+[control]
+read_only = yes
-[menu:verifying_new_password]
-prompt = Enter the same new password again:
-echo = off
+[menu:active]
+prompt = >
[menu:checking_new_account_name]
+action_d = user.account.delete()
+action_g = user.account.delete()
+branch_d = disconnecting
+branch_g = entering_account_name
+branch_n = entering_new_password
choice_d = disconnect now
choice_g = go back
-prompt = Enter your choice:
-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?
-default = d
choice_n = new account
+default = d
+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?
+prompt = Enter your choice:
-[menu:disconnecting_duplicates]
-prompt = $(red)Closing your previous connection...$(nrm)$(eol)
+[menu:checking_password]
+echo = off
+error_incorrect = Incorrect password, please try again...
+prompt = Password:
+
+[menu:choose_gender]
+action = user.avatar.set("gender", user.menu_choices[choice])
+branch = choose_name
+choice_f = female
+choice_m = male
+description = First, your new avatar needs a gender. In the world of Example, all avatars are either male or female.
+prompt = Pick a gender for your new avatar:
[menu:choose_name]
-prompt = Choose a name for $(tpop):
-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.
+action = user.avatar.set("name", user.menu_choices[choice])
+branch = main_utility
+branch_m = choose_name
+choice_m = generate more names
create_1 = random_name()
create_3 = random_name()
create_2 = random_name()
create_4 = random_name()
create_7 = random_name()
create_6 = random_name()
-branch = active
-action = user.avatar.set("name", user.menu_choices[choice])
+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.
+prompt = Choose a name for $(tpop):
+
+[menu:disconnecting]
+description = $(red)Disconnecting...$(nrm)
+
+[menu:disconnecting_duplicates]
+prompt = $(red)Closing your previous connection...$(nrm)$(eol)
[menu:entering_account_name]
-prompt = Identify yourself:
description = Welcome to the mudpy example...
error_bad_name = Your account name needs to contain only digits (0-9) and letters (a-z).
+prompt = Identify yourself:
-[menu:disconnecting]
-description = $(red)Disconnecting...$(nrm)
-
-[menu:active]
-prompt = >
+[menu:entering_new_password]
+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.
+prompt = Enter a new password for "$(account)":
+error_differs = The two passwords did not match. Try again...
[menu:main_utility]
-choice_d = delete an unwanted avatar
-branch_d = delete_avatar
action_c = user.new_avatar()
-description = From here you can activate, create and delete avatars. An avatar is your persona in the world of Example.
+branch_a = active
branch_c = choose_gender
-choice_c = create a new avatar
-choice_l = leave example for now
+branch_d = delete_avatar
branch_l = disconnecting
-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.
-choice_p = permanently remove your account
branch_p = delete_account
-branch_a = active
choice_a = activate an existing avatar
+choice_c = create a new avatar
+choice_d = delete an unwanted avatar
+choice_l = leave example for now
+choice_p = permanently remove your account
+demand_a = user.account.get("avatars")
+demand_c = len(user.account.getlist("avatars")) < universe.categories["internal"]["limits"].getint("max_avatars")
+demand_d = user.account.get("avatars")
+description = From here you can activate, create and delete avatars. An avatar is your persona in the world of Example.
+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.
+prompt = What would you like to do?
-[menu:checking_password]
-prompt = Password:
-error_incorrect = Incorrect password, please try again...
+[menu:verifying_new_password]
echo = off
+prompt = Enter the same new password again:
# Licensed per terms in the LICENSE file distributed with this software.
# import some things we need
-from ConfigParser import SafeConfigParser
+from ConfigParser import RawConfigParser
from md5 import new as new_md5
-from os import F_OK, R_OK, access, getcwd, makedirs, sep
+from os import R_OK, access, chmod, makedirs, stat
+from os.path import abspath, dirname, exists, isabs, join as path_join
from random import choice, randrange
from socket import AF_INET, SO_REUSEADDR, SOCK_STREAM, SOL_SOCKET, socket
+from stat import S_IMODE, ST_MODE
from time import asctime, sleep
-# a dict of replacement macros
-macros = {
- "$(eol)": "\r\n",
- "$(bld)": chr(27) + "[1m",
- "$(nrm)": chr(27) + "[0m",
- "$(blk)": chr(27) + "[30m",
- "$(grn)": chr(27) + "[32m",
- "$(red)": chr(27) + "[31m"
- }
-
class Element:
"""An element of the universe."""
def __init__(self, key, universe, origin=""):
universe.categories[self.category][self.subkey] = self
self.origin = origin
if not self.origin: self.origin = universe.default_origins[self.category]
- if not self.origin.startswith(sep):
- self.origin = getcwd() + sep + self.origin
+ if not isabs(self.origin):
+ self.origin = abspath(self.origin)
universe.contents[self.key] = self
if not self.origin in universe.files:
DataFile(self.origin, universe)
def facets(self):
"""Return a list of facets for this element."""
return universe.files[self.origin].data.options(self.key)
- def get(self, facet):
+ def get(self, facet, default=""):
"""Retrieve values."""
if universe.files[self.origin].data.has_option(self.key, facet):
return universe.files[self.origin].data.get(self.key, facet)
- else:
- return ""
+ else: return default
def getboolean(self, facet, default=False):
"""Retrieve values as boolean type."""
if universe.files[self.origin].data.has_option(self.key, facet):
return universe.files[self.origin].data.getboolean(self.key, facet)
- else:
- return default
- def getint(self, facet):
- """Convenience method to coerce return values as type int."""
+ else: return default
+ def getint(self, facet, default=0):
+ """Return values as int/long type."""
+ if universe.files[self.origin].data.has_option(self.key, facet):
+ return universe.files[self.origin].data.getint(self.key, facet)
+ else: return default
+ def getfloat(self, facet, default=0.0):
+ """Return values as float type."""
+ if universe.files[self.origin].data.has_option(self.key, facet):
+ return universe.files[self.origin].data.getfloat(self.key, facet)
+ else: return default
+ def getlist(self, facet, default=[]):
+ """Return values as list type."""
value = self.get(facet)
- if not value: value = 0
- elif type(value) is str: value = value.rstrip("L")
- return int(value)
- def getfloat(self, facet):
- """Convenience method to coerce return values as type float."""
+ if not value: return default
+ else: return makelist(value)
+ def getdict(self, facet, default={}):
+ """Return values as dict type."""
value = self.get(facet)
- if not value: value = 0
- elif type(value) is str: value = value.rstrip("L")
- return float(value)
+ if not value: return default
+ else: return makedict(value)
def set(self, facet, value):
"""Set values."""
- if not type(value) is str: value = repr(value)
+ if type(value) is long: value = repr(value).rstrip("L")
+ elif not type(value) is str: value = repr(value)
universe.files[self.origin].data.set(self.key, facet, value)
class DataFile:
"""A file containing universe elements."""
def __init__(self, filename, universe):
- filedir = sep.join(filename.split(sep)[:-1])
- self.data = SafeConfigParser()
+ self.data = RawConfigParser()
if access(filename, R_OK): self.data.read(filename)
self.filename = filename
universe.files[filename] = self
- if "categories" in self.data.sections():
- for option in self.data.options("categories"):
- universe.default_origins[option] = self.data.get("categories", option)
- if not option in universe.categories:
- universe.categories[option] = {}
+ if self.data.has_option("control", "include_files"):
+ includes = makelist(self.data.get("control", "include_files"))
+ else: includes = []
+ if self.data.has_option("control", "default_files"):
+ origins = makedict(self.data.get("control", "default_files"))
+ for key in origins.keys():
+ if not key in includes: includes.append(key)
+ universe.default_origins[key] = origins[key]
+ if not key in universe.categories:
+ universe.categories[key] = {}
+ if self.data.has_option("control", "private_files"):
+ for item in makelist(self.data.get("control", "private_files")):
+ if not item in includes: includes.append(item)
+ if not item in universe.private_files:
+ if not isabs(item):
+ item = path_join(dirname(filename), item)
+ universe.private_files.append(item)
for section in self.data.sections():
- if section == "categories" or section == "include":
- for option in self.data.options(section):
- includefile = self.data.get(section, option)
- if not includefile.startswith(sep):
- includefile = filedir + sep + includefile
- DataFile(includefile, universe)
- elif section != "control":
+ if section != "control":
Element(section, universe, filename)
+ for include_file in includes:
+ if not isabs(include_file):
+ include_file = path_join(dirname(filename), include_file)
+ DataFile(include_file, universe)
def save(self):
- if self.data.sections() and not ( "control" in self.data.sections() and self.data.getboolean("control", "read_only") ):
- basedir = sep.join(self.filename.split(sep)[:-1])
- if not access(basedir, F_OK): makedirs(basedir)
+ if self.data.sections() and ( not self.data.has_option("control", "read_only") or not self.data.getboolean("control", "read_only") ):
+ if not exists(dirname(self.filename)): makedirs(dirname)
file_descriptor = file(self.filename, "w")
+ if self.filename in universe.private_files and oct(S_IMODE(stat(self.filename)[ST_MODE])) != 0600:
+ chmod(self.filename, 0600)
self.data.write(file_descriptor)
file_descriptor.flush()
file_descriptor.close()
self.contents = {}
self.default_origins = {}
self.files = {}
+ self.private_files = []
self.userlist = []
self.terminate_world = False
self.reload_modules = False
]
for filename in possible_filenames:
if access(filename, R_OK): break
- if not filename.startswith(sep):
- filename = getcwd() + sep + filename
+ if not isabs(filename):
+ filename = abspath(filename)
DataFile(filename, self)
def save(self):
"""Save the universe to persistent storage."""
self.last_address = ""
self.connection = None
self.authenticated = False
- self.password_tries = 1
+ self.password_tries = 0
self.state = "entering_account_name"
self.menu_seen = False
self.error = ""
def quit(self):
"""Log, close the connection and remove."""
- name = self.account.get("name")
+ if self.account: name = self.account.get("name")
+ else: name = ""
if name: message = "User " + name
else: message = "An unnamed user"
message += " logged out."
while "avatar:" + repr(counter + 1) in universe.categories["actor"].keys(): counter += 1
universe.categories["internal"]["counters"].set("next_avatar", counter + 1)
self.avatar = Element("actor:avatar:" + repr(counter), universe)
- avatars = self.account.get("avatars").split()
+ avatars = self.account.getlist("avatars")
avatars.append(self.avatar.key)
- self.account.set("avatars", " ".join(avatars))
+ self.account.set("avatars", avatars)
def list_avatar_names(self):
"""A test function to list names of assigned avatars."""
- try:
- avatars = self.account.get("avatars").split()
- except:
- avatars = []
+ avatars = self.account.getlist("avatars")
avatar_names = []
for avatar in avatars:
avatar_names.append(universe.contents[avatar].get("name"))
return avatar_names
+def makelist(value):
+ """Turn string into list type."""
+ if value[0] + value[-1] == "[]": return eval(value)
+ else: return [ value ]
+
+def makedict(value):
+ """Turn string into dict type."""
+ if value[0] + value[-1] == "{}": return eval(value)
+ elif value.find(":") > 0: return eval("{" + value + "}")
+ else: return { value: None }
+
def broadcast(message):
"""Send a message to all connected users."""
for each_user in universe.userlist: each_user.send("$(eol)" + message)
def replace_macros(user, text, is_input=False):
"""Replaces macros in text output."""
+
+ # loop until broken
while True:
+
+ # third person pronouns
+ pronouns = {
+ "female": { "obj": "her", "pos": "hers", "sub": "she" },
+ "male": { "obj": "him", "pos": "his", "sub": "he" },
+ "neuter": { "obj": "it", "pos": "its", "sub": "it" }
+ }
+
+ # a dict of replacement macros
+ macros = {
+ "$(eol)": "\r\n",
+ "$(bld)": chr(27) + "[1m",
+ "$(nrm)": chr(27) + "[0m",
+ "$(blk)": chr(27) + "[30m",
+ "$(grn)": chr(27) + "[32m",
+ "$(red)": chr(27) + "[31m",
+ }
+
+ # add dynamic macros where possible
+ if user.account:
+ account_name = user.account.get("name")
+ if account_name:
+ macros["$(account)"] = account_name
+ if user.avatar:
+ avatar_gender = user.avatar.get("gender")
+ if avatar_gender:
+ macros["$(tpop)"] = pronouns[avatar_gender]["obj"]
+ macros["$(tppp)"] = pronouns[avatar_gender]["pos"]
+ macros["$(tpsp)"] = pronouns[avatar_gender]["sub"]
+
+ # find and replace per the macros dict
macro_start = text.find("$(")
if macro_start == -1: break
macro_end = text.find(")", macro_start) + 1
if macro in macros.keys():
text = text.replace(macro, macros[macro])
- # the user's account name
- elif macro == "$(account)":
- text = text.replace(macro, user.account.get("name"))
-
- # third person subjective pronoun
- elif macro == "$(tpsp)":
- if user.avatar.get("gender") == "male":
- text = text.replace(macro, "he")
- elif user.avatar.get("gender") == "female":
- text = text.replace(macro, "she")
- else:
- text = text.replace(macro, "it")
-
- # third person objective pronoun
- elif macro == "$(tpop)":
- if user.avatar.get("gender") == "male":
- text = text.replace(macro, "him")
- elif user.avatar.get("gender") == "female":
- text = text.replace(macro, "her")
- else:
- text = text.replace(macro, "it")
-
- # third person possessive pronoun
- elif macro == "$(tppp)":
- if user.avatar.get("gender") == "male":
- text = text.replace(macro, "his")
- elif user.avatar.get("gender") == "female":
- text = text.replace(macro, "hers")
- else:
- text = text.replace(macro, "its")
-
# if we get here, log and replace it with null
else:
text = text.replace(macro, "")
def get_menu_choices(user):
"""Return a dict of choice:meaning."""
choices = {}
+ ignores = []
+ options = {}
+ creates = {}
for facet in universe.categories["menu"][user.state].facets():
- if facet.startswith("choice_"):
- choices[facet.split("_", 2)[1]] = universe.categories["menu"][user.state].get(facet)
+ if facet.startswith("demand_") and not eval(universe.categories["menu"][user.state].get(facet)):
+ ignores.append(facet.split("_", 2)[1])
+ elif facet.startswith("choice_"):
+ options[facet] = facet.split("_", 2)[1]
elif facet.startswith("create_"):
- choices[facet.split("_", 2)[1]] = eval(universe.categories["menu"][user.state].get(facet))
+ creates[facet] = facet.split("_", 2)[1]
+ for facet in options.keys():
+ if not options[facet] in ignores:
+ choices[options[facet]] = universe.categories["menu"][user.state].get(facet)
+ for facet in creates.keys():
+ if not creates[facet] in ignores:
+ choices[creates[facet]] = eval(universe.categories["menu"][user.state].get(facet))
return choices
def get_formatted_menu_choices(state, choices):
if choice: choice = choice.lower()
else: choice = ""
- # run any script related to this choice
- exec(get_choice_action(user, choice))
-
- # move on to the next state or return an error
- new_state = get_choice_branch(user, choice)
- if new_state: user.state = new_state
+ if choice in user.menu_choices:
+ exec(get_choice_action(user, choice))
+ new_state = get_choice_branch(user, choice)
+ if new_state: user.state = new_state
else: user.error = "default"
def handler_entering_account_name(user):
user.state = "main_utility"
# if at first your hashes don't match, try, try again
- elif user.password_tries < universe.categories["internal"]["general"].getint("password_tries"):
+ elif user.password_tries < universe.categories["internal"]["limits"].getint("password_tries") - 1:
user.password_tries += 1
user.error = "incorrect"
user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
user.state = "disconnecting"
-def handler_checking_new_account_name(user):
- """Handle input for the new user menu."""
-
- # get the next waiting line of input
- input_data = user.input_queue.pop(0)
-
- # if there's input, take the first character and lowercase it
- if input_data:
- choice = input_data.lower()[0]
-
- # if there's no input, use the default
- else:
- choice = get_default_menu_choice(user.state)
-
- # user selected to disconnect
- if choice == "d":
- user.account.delete()
- user.state = "disconnecting"
-
- # go back to the login screen
- elif choice == "g":
- user.account.delete()
- user.state = "entering_account_name"
-
- # new user, so ask for a password
- elif choice == "n":
- user.state = "entering_new_password"
-
- # user entered a non-existent option
- else:
- user.error = "default"
-
def handler_entering_new_password(user):
"""Handle a new password entry."""
user.state = "verifying_new_password"
# the password was weak, try again if you haven't tried too many times
- elif user.password_tries < universe.categories["internal"]["general"].getint("password_tries"):
+ elif user.password_tries < universe.categories["internal"]["limits"].getint("password_tries") - 1:
user.password_tries += 1
user.error = "weak"
# go back to entering the new password as long as you haven't tried
# too many times
- elif user.password_tries < universe.categories["internal"]["general"].getint("password_tries"):
+ elif user.password_tries < universe.categories["internal"]["limits"].getint("password_tries") - 1:
user.password_tries += 1
user.error = "differs"
user.state = "entering_new_password"
for facet in universe.categories["internal"]["language"].facets():
if facet.startswith("punctuation_"):
action = facet.split("_")[1]
- for mark in universe.categories["internal"]["language"].get(facet).split():
+ for mark in universe.categories["internal"]["language"].getlist(facet):
actions[mark] = action
# match the punctuation used, if any, to an action
message += default_punctuation
# capitalize a list of words within the message
- capitalize = universe.categories["internal"]["language"].get("capitalize").split()
- for word in capitalize:
+ capitalize_words = universe.categories["internal"]["language"].getlist("capitalize_words")
+ for word in capitalize_words:
message = message.replace(" " + word + " ", " " + word.capitalize() + " ")
# tell the room