Imported from archive.
authorJeremy Stanley <fungi@yuggoth.org>
Mon, 18 Jul 2005 22:24:29 +0000 (22:24 +0000)
committerJeremy Stanley <fungi@yuggoth.org>
Mon, 18 Jul 2005 22:24:29 +0000 (22:24 +0000)
* Initial commit of working project files.

15 files changed:
bin/muff [new file with mode: 0755]
doc/license [new file with mode: 0644]
etc/muff.conf [new file with mode: 0644]
lib/commands/active [new file with mode: 0644]
lib/menus/account_creation [new file with mode: 0644]
lib/menus/active [new file with mode: 0644]
lib/menus/login [new file with mode: 0644]
lib/muff/__init__.py [new file with mode: 0644]
lib/muff/muffcmds.py [new file with mode: 0644]
lib/muff/muffconf.py [new file with mode: 0644]
lib/muff/muffmain.py [new file with mode: 0644]
lib/muff/muffmenu.py [new file with mode: 0644]
lib/muff/muffmisc.py [new file with mode: 0644]
lib/muff/muffsock.py [new file with mode: 0644]
lib/muff/muffuser.py [new file with mode: 0644]

diff --git a/bin/muff b/bin/muff
new file mode 100755 (executable)
index 0000000..64aab9d
--- /dev/null
+++ b/bin/muff
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+"""Skeletal executable for the MUFF Engine"""
+
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#    - Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#    - Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials provided
+#      with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import ConfigParser
+import sys
+
+def get_main_loop():
+       """Find and return the main loop function"""
+
+       # figure out where to find our python modules
+       config_data = ConfigParser.SafeConfigParser()
+       config_dirs = [".", "./etc", "/usr/local/muff", "/usr/local/muff/etc", "/etc/muff", "/etc" ]
+       config_name = "muff.conf"
+       config_files = []
+       for each_dir in config_dirs:
+               config_files.append(each_dir + "/" + config_name)
+       config_data.read(config_files)
+       modules = config_data.get("general", "modules")
+
+       # add it to the load path
+       sys.path.append(modules)
+
+       # import the main loop function
+       from muff.muffmain import main
+       return main
+
+# load the main loop
+main = get_main_loop()
+
+# run the main loop
+main()
+
diff --git a/doc/license b/doc/license
new file mode 100644 (file)
index 0000000..09499ea
--- /dev/null
@@ -0,0 +1,27 @@
+Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+   - Redistributions of source code must retain the above copyright
+     notice, this list of conditions and the following disclaimer.
+   - Redistributions in binary form must reproduce the above
+     copyright notice, this list of conditions and the following
+     disclaimer in the documentation and/or other materials provided
+     with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/etc/muff.conf b/etc/muff.conf
new file mode 100644 (file)
index 0000000..ca879e7
--- /dev/null
@@ -0,0 +1,12 @@
+[general]
+account_path = ./lib/accounts
+command_path = ./lib/commands
+increment = 0.1
+menu_path = ./lib/menus
+modules = ./lib
+password_tries = 3
+
+[network]
+host =
+port = 6669
+
diff --git a/lib/commands/active b/lib/commands/active
new file mode 100644 (file)
index 0000000..885520e
--- /dev/null
@@ -0,0 +1,20 @@
+[halt]
+description = Shut down the world.
+help = This will save all active accounts, disconnect all clients and stop the entire program.
+
+[help]
+description = List commands or get help on one.
+help = This will list all comand words available to you along with a brief description or, alternatively, give you detailed information on one command.
+
+[quit]
+description = Leave Example.
+help = This will save your account and disconnect your client connection.
+
+[reload]
+description = Reload code modules and data.
+help = This will reload all python code modules, reload configuration files and re-read data files.
+
+[say]
+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.
+
diff --git a/lib/menus/account_creation b/lib/menus/account_creation
new file mode 100644 (file)
index 0000000..dc7f032
--- /dev/null
@@ -0,0 +1,18 @@
+[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
+choice_n = new account
+prompt = Enter your choice:
+default = d
+
+[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]
+prompt = Enter the same new password again:
+echo = off
+
diff --git a/lib/menus/active b/lib/menus/active
new file mode 100644 (file)
index 0000000..333dd9f
--- /dev/null
@@ -0,0 +1,3 @@
+[active]
+prompt = >
+
diff --git a/lib/menus/login b/lib/menus/login
new file mode 100644 (file)
index 0000000..1acf366
--- /dev/null
@@ -0,0 +1,9 @@
+[entering account name]
+description = Welcome to the mudpy example...
+prompt = Identify yourself:
+
+[checking password]
+prompt = Password:
+echo = off
+error_incorrect = Incorrect password, please try again...
+
diff --git a/lib/muff/__init__.py b/lib/muff/__init__.py
new file mode 100644 (file)
index 0000000..27df656
--- /dev/null
@@ -0,0 +1,31 @@
+"""Initialization for MUFF Modules"""
+
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#    - Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#    - Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials provided
+#      with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+__all__ = [ "muffcmds", "muffconf", "muffmain", "muffmenu", "muffmisc", "muffsock", "muffuser", "muffvars" ]
+
diff --git a/lib/muff/muffcmds.py b/lib/muff/muffcmds.py
new file mode 100644 (file)
index 0000000..a9df032
--- /dev/null
@@ -0,0 +1,226 @@
+"""Command objects for the MUFF Engine"""
+
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#    - Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#    - Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials provided
+#      with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import ConfigParser
+import md5
+import os
+import random
+import string
+
+import muff
+for module in muff.__all__:
+       exec("import " + module)
+
+try:
+       if muffconf.config_data.get("general", "command_path"):
+               pass
+except AttributeError:
+       reload(muffconf)
+command_path = muffconf.config_data.get("general", "command_path")
+command_files = []
+for each_file in os.listdir(command_path):
+       command_files.append(command_path + "/" + each_file)
+command_data = ConfigParser.SafeConfigParser()
+command_data.read(command_files)
+command_list = command_data.sections()
+
+def handle_user_input(user, input):
+       if user.state == "active": handler_active(user, input)
+       elif user.state == "entering account name": handler_entering_account_name(user, input)
+       elif user.state == "checking password": handler_checking_password(user, input)
+       elif user.state == "checking new account name": handler_checking_new_account_name(user, input)
+       elif user.state == "entering new password": handler_entering_new_password(user, input)
+       elif user.state == "verifying new password": handler_verifying_new_password(user, input)
+       else: handler_fallthrough(user, input)
+       user.menu_seen = False
+
+def handler_entering_account_name(user, input):
+       if input:
+               user.proposed_name = string.split(input)[0].lower()
+               user.get_passhash()
+               if user.passhash:
+                       user.state = "checking password"
+               else:
+                       user.name = user.proposed_name
+                       user.proposed_name = None
+                       user.load()
+                       user.state = "checking new account name"
+       else:
+               command_quit(user)
+
+def handler_checking_password(user, input):
+       if md5.new(user.proposed_name + input).hexdigest() == user.passhash:
+               user.name = user.proposed_name
+               user.proposed_name = None
+               user.load()
+               user.state = "active"
+       elif user.password_tries < muffconf.config_data.getint("general", "password_tries"):
+               user.password_tries += 1
+               user.error = "incorrect"
+       else:
+               user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
+               command_quit(user)
+
+def handler_checking_new_account_name(user, input):
+       if input:
+               choice = input.lower()[0]
+       else:
+               choice = muffmenu.get_default(user)
+       if choice == "d":
+               command_quit(user)
+       elif choice == "g":
+               user.state = "entering account name"
+       elif choice == "n":
+               user.state = "entering new password"
+       else:
+               user.error = "default"
+
+def handler_entering_new_password(user, input):
+       if len(input) > 6 and len(filter(lambda x: x>="0" and x<="9", input)) and len(filter(lambda x: x>="A" and x<="Z", input)) and len(filter(lambda x: x>="a" and x<="z", input)):
+               user.passhash = md5.new(user.name + input).hexdigest()
+               user.state = "verifying new password"
+       elif user.password_tries < muffconf.config_data.getint("general", "password_tries"):
+               user.password_tries += 1
+               user.error = "weak"
+       else:
+               user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
+               command_quit(user)
+
+def handler_verifying_new_password(user, input):
+       if md5.new(user.name + input).hexdigest() == user.passhash:
+               user.state = "active"
+       elif user.password_tries < muffconf.config_data.getint("general", "password_tries"):
+               user.password_tries += 1
+               user.error = "differs"
+               user.state = "entering new password"
+       else:
+               user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
+               command_quit(user)
+
+def handler_active(user, input):
+       if not user.authenticated: user.authenticated = True
+       try:
+               inputlist = string.split(input, None, 1)
+               command = inputlist[0]
+       except:
+               command = input
+       try:
+               parameters = inputlist[1]
+       except:
+               parameters = ""
+       command = command.lower()
+       if not command: command_null(user, command, parameters)
+       elif command in command_list: exec("command_" + command + "(user, command, parameters)")
+       else: command_error(user, command, parameters)
+
+def handler_fallthrough(user, input):
+       if input:
+               print("User \"" + user + "\" entered \"" + input + "\" while in unknown state \"" + user.state + "\".")
+
+def command_halt(user, command="", parameters=""):
+       muffmisc.broadcast(user.name + " halts the world.")
+       for each_user in muffvars.userlist:
+               each_user.save()
+       muffvars.terminate_world = True
+
+def command_reload(user, command="", parameters=""):
+       user.send("Reloading all code modules.")
+       muffvars.reload_modules = True
+
+def command_quit(user, command="", parameters=""):
+       user.save()
+       user.connection.close()
+       user.remove()
+
+def command_help(user, command="", parameters=""):
+       if parameters:
+               if parameters in command_list:
+                       if command_data.has_section(parameters):
+                               try:
+                                       description = command_data.get(parameters, "description")
+                               except:
+                                       description = "(no short description provided)"
+                               output = "$(grn)" + parameters + "$(nrm) - " + command_data.get(parameters, "description") + "$(eol)$(eol)"
+                               try:
+                                       help_text = command_data.get(parameters, "help")
+                               except:
+                                       help_text = "No help is provided for this command."
+                               output += help_text
+                       else:
+                               output = "There is no information on that command."
+               else:
+                       output = "That is not an available command."
+                       
+       else:
+               output = "These are the commands available to you:$(eol)$(eol)"
+               sorted_commands = command_list
+               sorted_commands.sort()
+               for item in sorted_commands:
+                       try:
+                               description = command_data.get(item, "description")
+                       except:
+                               description = "(no short description provided)"
+                       output += "   $(grn)" + item + "$(nrm) - " + command_data.get(item, "description") + "$(eol)"
+               output += "$(eol)Enter \"help COMMAND\" for help on a command named \"COMMAND\"."
+       user.send(output)
+
+def command_say(user, command="", parameters=""):
+       message = parameters.strip("\"'`").capitalize()
+       if parameters:
+               if message[-1] == "!":
+                       action = "exclaim"
+               elif message[-1] in [ ",", "-", ":", ";" ]:
+                       action = "begin"
+               elif message[-3:] == "...":
+                       action = "muse"
+               elif message[-1] == "?":
+                       action = "ask"
+               else:
+                       action = "say"
+                       message += "."
+               capitalization = [ "i", "i'd", "i'll" ]
+               for word in capitalization:
+                       message = message.replace(" " + word + " ", " " + word.capitalize() + " ")
+               muffmisc.broadcast(user.name + " " + action + "s, \"" + message + "\"")
+       else:
+               user.send("What do you want to say?")
+
+def command_null(user, command="", parameters=""):
+       pass
+
+def command_error(user, command="", parameters=""):
+       if random.random() > 0.1:
+               message = "I'm not sure what \"" + command
+               if parameters:
+                       message += " " + parameters
+               message += "\" means..."
+       else:
+               message = "Arglebargle, glop-glyf!?!"
+       user.send(message)
+
diff --git a/lib/muff/muffconf.py b/lib/muff/muffconf.py
new file mode 100644 (file)
index 0000000..44c344c
--- /dev/null
@@ -0,0 +1,45 @@
+"""Configuration objects for the MUFF Engine"""
+
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#    - Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#    - Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials provided
+#      with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+import ConfigParser
+import string
+
+import muff
+for module in muff.__all__:
+       exec("import " + module)
+
+config_data = ConfigParser.SafeConfigParser()
+config_dirs = [".", "./etc", "/usr/local/muff", "/usr/local/muff/etc", "/etc/muff", "/etc" ]
+config_name = "muff.conf"
+config_files = []
+for each_dir in config_dirs:
+       config_files.append(each_dir + "/" + config_name)
+config_data.read(config_files)
+
diff --git a/lib/muff/muffmain.py b/lib/muff/muffmain.py
new file mode 100644 (file)
index 0000000..0b6707e
--- /dev/null
@@ -0,0 +1,69 @@
+"""Main loop for the MUFF Engine"""
+
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#    - Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#    - Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials provided
+#      with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import string
+import time
+
+import muff
+for module in muff.__all__:
+       exec("import " + module)
+
+def main():
+       while not muffvars.terminate_world:
+               if not muffvars.newsocket:
+                       muffsock.initialize_server_socket()
+               time.sleep(muffconf.config_data.getfloat("general", "increment"))
+               if muffvars.reload_modules:
+                       reload(muff)
+                       for module in muff.__all__:
+                               exec("reload(muff." + module + ")")
+                       muffvars.reload_modules = 0
+               user = muffsock.check_for_connection(muffvars.newsocket)
+               if user:
+                       muffvars.userlist.append(user)
+                       print len(muffvars.userlist),"connection(s)"
+               for each_user in muffvars.userlist:
+                       each_user.show_menu()
+                       input_data = ""
+                       try:
+                               input_data = each_user.connection.recv(1024)
+                       except:
+                               pass
+                       if input_data:
+                               each_user.partial_input += input_data
+                               if each_user.partial_input and each_user.partial_input[-1] == "\n":
+                                       each_user.partial_input = filter(lambda x: x>=' ' and x<='~', each_user.partial_input)
+                                       each_user.partial_input = string.strip(each_user.partial_input)
+                                       each_user.input_queue.append(each_user.partial_input)
+                                       each_user.partial_input = ""
+                                       muffcmds.handle_user_input(each_user, each_user.input_queue[0])
+                                       each_user.input_queue.remove(each_user.input_queue[0])
+       muffsock.destroy_all_sockets()
+       print "Shutting down now."
+
diff --git a/lib/muff/muffmenu.py b/lib/muff/muffmenu.py
new file mode 100644 (file)
index 0000000..0a9989b
--- /dev/null
@@ -0,0 +1,112 @@
+"""Menu objects for the MUFF Engine"""
+
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#    - Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#    - Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials provided
+#      with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import ConfigParser
+import os
+import re
+import string
+
+import muff
+for module in muff.__all__:
+       exec("import " + module)
+
+try:
+       if muffconf.config_data.get("general", "menu_path"):
+               pass
+except AttributeError:
+       reload(muffconf)
+
+menu_files = []
+menu_path = muffconf.config_data.get("general", "menu_path")
+for each_file in os.listdir(menu_path):
+       menu_files.append(menu_path + "/" + each_file)
+menu_data = ConfigParser.SafeConfigParser()
+menu_data.read(menu_files)
+
+def get_default(user):
+       return menu_data.get(user.state, "default")
+
+def get_menu(user):
+       if not user.menu_seen:
+               if user.echoing:
+                       spacer = ""
+               else:
+                       spacer = "$(eol)"
+               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 = ""
+               except:
+                       if not user.echoing:
+                               echo = chr(255) + chr(252) + chr(1) + chr(0)
+                               user.echoing = True
+                       else:
+                               echo = ""
+               if user.error:
+                       try:
+                               description = "$(red)" + menu_data.get(user.state, "error_" + user.error) + "$(nrm)$(eol)$(eol)"
+                       except:
+                               description = "$(red)That is not a valid choice...$(nrm)$(eol)$(eol)"
+                       user.error = ""
+               else:
+                       try:
+                               description = menu_data.get(user.state, "description") + "$(eol)$(eol)"
+                       except:
+                               description = ""
+               try:
+                       choices = {}
+                       for option in menu_data.options(user.state):
+                               if re.match("choice_", option):
+                                       choices[option.split("_")[1]] = menu_data.get(user.state, option)
+                       choice_keys = choices.keys()
+                       choice_keys.sort()
+                       choicestring = ""
+                       for choice in choice_keys:
+                               choicestring += "   [$(red)" + choice + "$(nrm)]  " + choices[choice] + "$(eol)"
+                       if choicestring:
+                               choicestring += "$(eol)"
+               except:
+                       choicestring = ""
+               try:
+                       prompt = menu_data.get(user.state, "prompt") + " "
+               except:
+                       prompt = ""
+               try:
+                       default = "[$(red)" + menu_data.get(user.state, "default") + "$(nrm)] "
+               except:
+                       default = ""
+               if user.echoing:
+                       echoing = ""
+               else:
+                       echoing = "(won't echo) "
+               user.send(echo + spacer + description + choicestring + prompt + default + echoing, "")
+               user.menu_seen = True
+
diff --git a/lib/muff/muffmisc.py b/lib/muff/muffmisc.py
new file mode 100644 (file)
index 0000000..6bfbe62
--- /dev/null
@@ -0,0 +1,63 @@
+"""Miscellaneous objects for the MUFF Engine"""
+
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#    - Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#    - Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials provided
+#      with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import string
+
+import muff
+for module in muff.__all__:
+       exec("import " + module)
+
+def broadcast(output):
+       for each_user in muffvars.userlist:
+               each_user.send(output)
+
+def wrap_ansi_text(text, width):
+       relative_position = 0
+       absolute_position = 0
+       escape = 0
+       for each_character in text:
+               if each_character == chr(27):
+                       escape = 1
+               elif escape:
+                       if each_character == "m":
+                               escape = 0
+               elif each_character == "\n":
+                       relative_position = 0
+               elif relative_position == width:
+                       wrap_offset = 0
+                       while text[absolute_position - wrap_offset] != " ":
+                               wrap_offset += 1
+                       text = text[:absolute_position - wrap_offset] + "\r\n" + text[absolute_position - wrap_offset + 1:]
+                       absolute_position += 1
+                       relative_position = wrap_offset
+               elif each_character != "\r":
+                       relative_position += 1
+               absolute_position += 1
+       return text
+
diff --git a/lib/muff/muffsock.py b/lib/muff/muffsock.py
new file mode 100644 (file)
index 0000000..1ca604c
--- /dev/null
@@ -0,0 +1,61 @@
+"""Socket objects for the MUFF Engine"""
+
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#    - Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#    - Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials provided
+#      with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import socket
+
+import muff
+for module in muff.__all__:
+       exec("import " + module)
+
+def check_for_connection(newsocket):
+       try:
+               connection, address = newsocket.accept()
+       except:
+               return None
+       print "Connection from", address
+       connection.setblocking(0)
+       user = muffuser.User();
+       user.connection = connection
+       user.address = address[0]
+       return user
+
+def initialize_server_socket():
+       newsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+       newsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+       newsocket.bind((muffconf.config_data.get("network", "host"), muffconf.config_data.getint("network", "port")))
+       newsocket.setblocking(0)
+       newsocket.listen(1)
+       print "Waiting for connection(s)..."
+       muffvars.newsocket = newsocket
+
+def destroy_all_sockets():
+       print "Closing remaining connections..."
+       for user in muffvars.userlist:
+               user.connection.close()
+
diff --git a/lib/muff/muffuser.py b/lib/muff/muffuser.py
new file mode 100644 (file)
index 0000000..4ecdebf
--- /dev/null
@@ -0,0 +1,103 @@
+"""User objects for the MUFF Engine"""
+
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#    - Redistributions of source code must retain the above copyright
+#      notice, this list of conditions and the following disclaimer.
+#    - Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials provided
+#      with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import ConfigParser
+import string
+
+import muff
+for module in muff.__all__:
+       exec("import " + module)
+
+class User:
+       def __init__(self):
+               self.name = ""
+               self.passhash = ""
+               self.address = ""
+               self.connection = None
+               self.authenticated = False
+               self.password_tries = 1
+               self.state = "entering account name"
+               self.menu_seen = False
+               self.error = ""
+               self.input_queue = []
+               self.output_queue = []
+               self.partial_input = ""
+               self.echoing = True
+               self.record = ConfigParser.SafeConfigParser()
+       def load(self):
+               filename = muffconf.config_data.get("general", "account_path") + "/" + self.name
+               try:
+                       self.record.read(filename)
+                       self.passhash = self.record.get("account", "passhash")
+                       self.last_address = self.record.get("account", "last_address", self.address)
+               except:
+                       pass
+       def get_passhash(self):
+               filename = muffconf.config_data.get("general", "account_path") + "/" + self.proposed_name
+               temporary_record = ConfigParser.SafeConfigParser()
+               try:
+                       temporary_record.read(filename)
+                       self.passhash = temporary_record.get("account", "passhash")
+               except:
+                       self.passhash = ""
+       def save(self):
+               if self.authenticated:
+                       filename = muffconf.config_data.get("general", "account_path") + "/" + self.name.lower()
+                       if not self.record.has_section("account"):
+                               self.record.add_section("account")
+                       self.record.set("account", "name", self.name)
+                       self.record.set("account", "passhash", self.passhash)
+                       self.record.set("account", "last_address", self.address)
+                       record_file = file(filename, "w")
+                       self.record.write(record_file)
+                       record_file.close()
+       def show_menu(self):
+               self.send(muffmenu.get_menu(self))
+       def remove(self):
+               muffvars.userlist.remove(self)
+       def send(self, output, eol="$(eol)"):
+               if output:
+                       output = "$(eol)" + output + eol
+                       output = string.replace(output, "$(eol)", "\r\n")
+                       output = string.replace(output, "$(div)", "\r\n\r\n")
+                       output = string.replace(output, "$(bld)", chr(27)+"[1m")
+                       output = string.replace(output, "$(nrm)", chr(27)+"[0m")
+                       output = string.replace(output, "$(blk)", chr(27)+"[30m")
+                       output = string.replace(output, "$(grn)", chr(27)+"[32m")
+                       output = string.replace(output, "$(red)", chr(27)+"[31m")
+                       output = string.replace(output, "$(account)", self.name)
+                       output = muffmisc.wrap_ansi_text(output, 80)
+                       self.output_queue.append(output)
+                       try:
+                               self.connection.send(self.output_queue[0])
+                               self.output_queue.remove(self.output_queue[0])
+                               self.menu_seen = False
+                       except:
+                               pass
+