Imported from archive.
[mudpy.git] / mudpy.py
index 9f5cd1d..06563f7 100644 (file)
--- a/mudpy.py
+++ b/mudpy.py
@@ -1,7 +1,8 @@
 """Core objects for the mudpy engine."""
 
-# Copyright (c) 2006 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
-# Licensed per terms in the LICENSE file distributed with this software.
+# Copyright (c) 2005, 2006 Jeremy Stanley <fungi@yuggoth.org>. All rights
+# reserved. Licensed per terms in the LICENSE file distributed with this
+# software.
 
 # import some things we need
 from ConfigParser import RawConfigParser
@@ -13,6 +14,7 @@ from re import match
 from signal import SIGHUP, SIGTERM, signal
 from socket import AF_INET, SO_REUSEADDR, SOCK_STREAM, SOL_SOCKET, socket
 from stat import S_IMODE, ST_MODE
+from string import digits, letters, punctuation, uppercase
 from sys import argv, stderr
 from syslog import LOG_PID, LOG_INFO, LOG_DAEMON, closelog, openlog, syslog
 from telnetlib import DO, DONT, ECHO, EOR, GA, IAC, LINEMODE, SB, SE, SGA, WILL, WONT
@@ -449,6 +451,7 @@ class Universe:
                        element.clean_contents()
 
        def new(self):
+               """Create a new, empty Universe (the Big Bang)."""
                new_universe = Universe()
                for attribute in vars(self).keys():
                        exec("new_universe." + attribute + " = self." + attribute)
@@ -646,7 +649,10 @@ class User:
                        # tack on a prompt if active
                        if self.state == "active":
                                if not just_prompt: output += "$(eol)"
-                               if add_prompt: output += "> "
+                               if add_prompt:
+                                       output += "> "
+                                       mode = self.avatar.get("mode")
+                                       if mode: output += "(" + mode + ") "
 
                        # find and replace macros in the output
                        output = replace_macros(self, output)
@@ -856,10 +862,11 @@ class User:
                """Have the active avatar leave the world."""
                if self.avatar:
                        current = self.avatar.get("location")
-                       self.avatar.set("default_location", current)
-                       self.avatar.echo_to_location("You suddenly wonder where " + self.avatar.get("name") + " went.")
-                       del universe.contents[current].contents[self.avatar.key]
-                       self.avatar.remove_facet("location")
+                       if current:
+                               self.avatar.set("default_location", current)
+                               self.avatar.echo_to_location("You suddenly wonder where " + self.avatar.get("name") + " went.")
+                               del universe.contents[current].contents[self.avatar.key]
+                               self.avatar.remove_facet("location")
                        self.avatar.owner = None
                        self.avatar = None
 
@@ -892,8 +899,6 @@ def log(message, level=0):
 
        # a couple references we need
        file_name = universe.categories["internal"]["logging"].get("file")
-       if not isabs(file_name):
-               file_name = path_join(universe.startdir, file_name)
        max_log_lines = universe.categories["internal"]["logging"].getint("max_log_lines")
        syslog_name = universe.categories["internal"]["logging"].get("syslog")
        timestamp = asctime()[4:19]
@@ -903,6 +908,8 @@ def log(message, level=0):
 
        # send the timestamp and line to a file
        if file_name:
+               if not isabs(file_name):
+                       file_name = path_join(universe.startdir, file_name)
                file_descriptor = file(file_name, "a")
                for line in lines: file_descriptor.write(timestamp + " " + line + "\n")
                file_descriptor.flush()
@@ -957,8 +964,8 @@ def get_loglines(level, start, stop):
                message = "There are " + str(total_count)
                message += " log lines in memory and " + str(filtered_count)
                message += " at or above level " + str(level) + "."
-               message += " The lines from " + str(stop) + " to " + str(start)
-               message += " are:$(eol)$(eol)"
+               message += " The matching lines from " + str(stop) + " to "
+               message += str(start) + " are:$(eol)$(eol)"
 
                # add the text from the selected lines
                if stop > 1: range_lines = loglines[-start:-(stop-1)]
@@ -1144,6 +1151,13 @@ def escape_macros(text):
        """Escapes replacement macros in text."""
        return text.replace("$(", "$_(")
 
+def first_word(text, separator=" "):
+       """Returns a tuple of the first word and the rest."""
+       if text:
+               if text.find(separator) > 0: return text.split(separator, 1)
+               else: return text, ""
+       else: return "", ""
+
 def on_pulse():
        """The things which should happen on each pulse, aside from reloads."""
 
@@ -1158,6 +1172,10 @@ def on_pulse():
        # iterate over the connected users
        for user in universe.userlist: user.pulse()
 
+       # add an element for counters if it doesn't exist
+       if not "counters" in universe.categories["internal"]:
+               universe.categories["internal"]["counters"] = Element("internal:counters", universe)
+
        # update the log every now and then
        if not universe.categories["internal"]["counters"].getint("mark"):
                log(str(len(universe.userlist)) + " connection(s)")
@@ -1523,12 +1541,16 @@ def handler_active(user):
        # is there input?
        if input_data:
 
-               # split out the command (first word) and parameters (everything else)
-               if input_data.find(" ") > 0:
-                       command_name, parameters = input_data.split(" ", 1)
+               # split out the command and parameters
+               actor = user.avatar
+               mode = actor.get("mode")
+               if mode and input_data.startswith("!"):
+                       command_name, parameters = first_word(input_data[1:])
+               elif mode == "chat":
+                       command_name = "say"
+                       parameters = input_data
                else:
-                       command_name = input_data
-                       parameters = ""
+                       command_name, parameters = first_word(input_data)
 
                # lowercase the command
                command_name = command_name.lower()
@@ -1539,7 +1561,6 @@ def handler_active(user):
                else: command = None
 
                # if it's allowed, do it
-               actor = user.avatar
                if actor.can_run(command): exec(command.get("action"))
 
                # otherwise, give an error
@@ -1608,6 +1629,24 @@ def command_help(actor, parameters):
                                help_text = "No help is provided for this command."
                        output += help_text
 
+                       # list related commands
+                       see_also = command.getlist("see_also")
+                       if see_also:
+                               really_see_also = ""
+                               for item in see_also:
+                                       if item in universe.categories["command"]:
+                                               command = universe.categories["command"][item]
+                                               if actor.can_run(command):
+                                                       if really_see_also:
+                                                               really_see_also += ", "
+                                                       if command.getboolean("administrative"):
+                                                               really_see_also += "$(red)"
+                                                       else:
+                                                               really_see_also += "$(grn)"
+                                                       really_see_also += item + "$(nrm)"
+                               if really_see_also:
+                                       output += "$(eol)$(eol)See also: " + really_see_also
+
                # no data for the requested command word
                else:
                        output = "That is not an available command."
@@ -1654,35 +1693,40 @@ def command_say(actor, parameters):
        # the user entered a message
        elif parameters:
 
-               # get rid of quote marks on the ends of the message and
-               # capitalize the first letter
+               # get rid of quote marks on the ends of the message
                message = parameters.strip("\"'`")
-               message = message[0].capitalize() + message[1:]
-
-               # a dictionary of punctuation:action pairs
-               actions = {}
-               for facet in universe.categories["internal"]["language"].facets():
-                       if facet.startswith("punctuation_"):
-                               action = facet.split("_")[1]
-                               for mark in universe.categories["internal"]["language"].getlist(facet):
-                                               actions[mark] = action
 
                # match the punctuation used, if any, to an action
+               actions = universe.categories["internal"]["language"].getdict("actions")
                default_punctuation = universe.categories["internal"]["language"].get("default_punctuation")
-               action = actions[default_punctuation]
+               action = ""
                for mark in actions.keys():
-                       if message.endswith(mark) and mark != default_punctuation:
+                       if message.endswith(mark):
                                action = actions[mark]
                                break
 
-               # if the action is default and there is no mark, add one
-               if action == actions[default_punctuation] and not message.endswith(default_punctuation):
-                       message += default_punctuation
+               # add punctuation if needed
+               if not action:
+                       action = actions[default_punctuation]
+                       if message and not message[-1] in punctuation:
+                               message += default_punctuation
+
+               # decapitalize the first letter to improve matching
+               if message and message[0] in uppercase:
+                       message = message[0].lower() + message[1:]
+
+               # iterate over all words in message, replacing typos
+               typos = universe.categories["internal"]["language"].getdict("typos")
+               words = message.split()
+               for index in range(len(words)):
+                       word = words[index]
+                       bare_word = word.strip(punctuation)
+                       if bare_word in typos.keys():
+                               words[index] = word.replace(bare_word, typos[bare_word])
+               message = " ".join(words)
 
-               # capitalize a list of words within the message
-               capitalize_words = universe.categories["internal"]["language"].getlist("capitalize_words")
-               for word in capitalize_words:
-                       message = message.replace(" " + word + " ", " " + word.capitalize() + " ")
+               # capitalize the first letter
+               message = message[0].upper() + message[1:]
 
                # tell the room
                actor.echo_to_location(actor.get("name") + " " + action + "s, \"" + message + "\"")
@@ -1692,6 +1736,17 @@ def command_say(actor, parameters):
        else:
                actor.send("What do you want to say?")
 
+def command_chat(actor):
+       """Toggle chat mode."""
+       mode = actor.get("mode")
+       if not mode:
+               actor.set("mode", "chat")
+               actor.send("Entering chat mode (use $(grn)!chat$(nrm) to exit).")
+       elif mode == "chat":
+               actor.remove_facet("mode")
+               actor.send("Exiting chat mode.")
+       else: actor.send("Sorry, but you're already busy with something else!")
+
 def command_show(actor, parameters):
        """Show program data."""
        message = ""
@@ -1870,9 +1925,9 @@ def create_pidfile(universe):
        pid = str(getpid())
        log("Process ID: " + pid)
        file_name = universe.contents["internal:process"].get("pidfile")
-       if not isabs(file_name):
-               file_name = path_join(universe.startdir, file_name)
        if file_name:
+               if not isabs(file_name):
+                       file_name = path_join(universe.startdir, file_name)
                file_descriptor = file(file_name, 'w')
                file_descriptor.write(pid + "\n")
                file_descriptor.flush()
@@ -1881,9 +1936,10 @@ def create_pidfile(universe):
 def remove_pidfile(universe):
        """Remove the file containing the current process ID."""
        file_name = universe.contents["internal:process"].get("pidfile")
-       if not isabs(file_name):
-               file_name = path_join(universe.startdir, file_name)
-       if file_name and access(file_name, W_OK): remove(file_name)
+       if file_name:
+               if not isabs(file_name):
+                       file_name = path_join(universe.startdir, file_name)
+               if access(file_name, W_OK): remove(file_name)
 
 def excepthook(excepttype, value, traceback):
        """Handle uncaught exceptions."""