Imported from archive.
[mudpy.git] / mudpy.py
index 01b9142..699eae1 100644 (file)
--- a/mudpy.py
+++ b/mudpy.py
@@ -1,8 +1,8 @@
 """Core objects for the mudpy engine."""
 
 """Core objects for the mudpy engine."""
 
-# Copyright (c) 2005, 2006 Jeremy Stanley <fungi@yuggoth.org>. All rights
-# reserved. Licensed per terms in the LICENSE file distributed with this
-# software.
+# Copyright (c) 2004-2008 Jeremy Stanley <fungi@yuggoth.org>. Permission
+# to use, copy, modify, and distribute this software is granted under
+# terms provided in the LICENSE file distributed with this software.
 
 # import some things we need
 from ConfigParser import RawConfigParser
 
 # import some things we need
 from ConfigParser import RawConfigParser
@@ -508,6 +508,7 @@ class User:
                self.error = ""
                self.input_queue = []
                self.last_address = ""
                self.error = ""
                self.input_queue = []
                self.last_address = ""
+               self.last_input = universe.get_time()
                self.menu_choices = {}
                self.menu_seen = False
                self.negotiation_pause = 0
                self.menu_choices = {}
                self.menu_seen = False
                self.negotiation_pause = 0
@@ -530,6 +531,27 @@ class User:
                self.connection.close()
                self.remove()
 
                self.connection.close()
                self.remove()
 
+       def check_idle(self):
+               """Warn or disconnect idle users as appropriate."""
+               idletime = universe.get_time() - self.last_input
+               linkdead_dict = universe.categories["internal"]["time"].getdict("linkdead")
+               if self.state in linkdead_dict: linkdead_state = self.state
+               else: linkdead_state = "default"
+               if idletime > linkdead_dict[linkdead_state]:
+                       self.send("$(eol)$(red)You've done nothing for far too long... goodbye!$(nrm)$(eol)", flush=True, add_prompt=False)
+                       logline = "Disconnecting "
+                       if self.account and self.account.get("name"): logline += self.account.get("name")
+                       else: logline += "an unknown user"
+                       logline += " after idling too long in a " + self.state + " state."
+                       log(logline, 2)
+                       self.state = "disconnecting"
+                       self.menu_seen = False
+               idle_dict = universe.categories["internal"]["time"].getdict("idle")
+               if self.state in idle_dict: idle_state = self.state
+               else: idle_state = "default"
+               if idletime == idle_dict[idle_state]:
+                       self.send("$(eol)$(red)If you continue to be unproductive, you'll be shown the door...$(nrm)$(eol)")
+
        def reload(self):
                """Save, load a new user and relocate the connection."""
 
        def reload(self):
                """Save, load a new user and relocate the connection."""
 
@@ -677,6 +699,9 @@ class User:
                        self.state = "disconnecting"
                        self.menu_seen = False
 
                        self.state = "disconnecting"
                        self.menu_seen = False
 
+               # check for an idle connection and act appropriately
+               else: self.check_idle()
+
                # if output is paused, decrement the counter
                if self.state == "initial":
                        if self.negotiation_pause: self.negotiation_pause -= 1
                # if output is paused, decrement the counter
                if self.state == "initial":
                        if self.negotiation_pause: self.negotiation_pause -= 1
@@ -992,21 +1017,40 @@ def wrap_ansi_text(text, width):
        # ignoring color escape sequences
        relative_position = 0
 
        # ignoring color escape sequences
        relative_position = 0
 
+       # whether the current character is part of a telnet IAC sequence
+       iac_counter = 0
+
        # whether the current character is part of a color escape sequence
        escape = False
 
        # iterate over each character from the begining of the text
        for each_character in text:
 
        # whether the current character is part of a color escape sequence
        escape = False
 
        # iterate over each character from the begining of the text
        for each_character in text:
 
+               # the current character is the telnet IAC character
+               if each_character == IAC and not iac_counter:
+                       iac_counter = 2
+
+               # the current character is within an IAC sequence
+               elif iac_counter:
+
+                       # the current character is another IAC,
+                       # terminating the sequence
+                       if each_character == IAC:
+                               iac_counter = 0
+
+                       # otherwise, decrement the IAC counter
+                       else:
+                               iac_counter -= 1
+
                # the current character is the escape character
                # the current character is the escape character
-               if each_character == chr(27):
+               elif each_character == chr(27) and not escape:
                        escape = True
 
                # the current character is within an escape sequence
                elif escape:
 
                        # the current character is m, which terminates the
                        escape = True
 
                # the current character is within an escape sequence
                elif escape:
 
                        # the current character is m, which terminates the
-                       # current escape sequence
+                       # escape sequence
                        if each_character == "m":
                                escape = False
 
                        if each_character == "m":
                                escape = False
 
@@ -1017,7 +1061,7 @@ def wrap_ansi_text(text, width):
 
                # the current character meets the requested maximum line width,
                # so we need to backtrack and find a space at which to wrap
 
                # the current character meets the requested maximum line width,
                # so we need to backtrack and find a space at which to wrap
-               elif relative_position == width:
+               elif relative_position == width and not each_character == "\r":
 
                        # distance of the current character examined from the
                        # relative position
 
                        # distance of the current character examined from the
                        # relative position
@@ -1092,56 +1136,75 @@ def random_name():
 def replace_macros(user, text, is_input=False):
        """Replaces macros in text output."""
 
 def replace_macros(user, text, is_input=False):
        """Replaces macros in text output."""
 
+       # 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",
+               "blu": chr(27) + "[34m",
+               "cyn": chr(27) + "[36m",
+               "grn": chr(27) + "[32m",
+               "mgt": chr(27) + "[35m",
+               "red": chr(27) + "[31m",
+               "yel": chr(27) + "[33m",
+               }
+
+       # 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"]
+
        # loop until broken
        while True:
 
        # 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",
-                       "$(blu)": chr(27) + "[34m",
-                       "$(cyn)": chr(27) + "[36m",
-                       "$(grn)": chr(27) + "[32m",
-                       "$(mgt)": chr(27) + "[35m",
-                       "$(red)": chr(27) + "[31m",
-                       "$(yel)": chr(27) + "[33m",
-                       }
-
-               # 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
                # find and replace per the macros dict
                macro_start = text.find("$(")
                if macro_start == -1: break
                macro_end = text.find(")", macro_start) + 1
-               macro = text[macro_start:macro_end]
+               macro = text[macro_start+2:macro_end-1]
                if macro in macros.keys():
                if macro in macros.keys():
-                       text = text.replace(macro, macros[macro])
+                       replacement = macros[macro]
+
+               # this is how we handle local file inclusion (dangerous!)
+               elif macro.startswith("inc:"):
+                       incfile = path_join(universe.startdir, macro[4:])
+                       if exists(incfile):
+                               incfd = file(incfile)
+                               replacement = ""
+                               for line in incfd:
+                                       if line.endswith("\n") and not line.endswith("\r\n"):
+                                               line = line.replace("\n", "\r\n")
+                                       replacement += line
+                               # lose the trailing eol
+                               replacement = replacement[:-2]
+                       else:
+                               replacement = ""
+                               log("Couldn't read included " + incfile + " file.", 6)
 
                # if we get here, log and replace it with null
                else:
 
                # if we get here, log and replace it with null
                else:
-                       text = text.replace(macro, "")
+                       replacement = ""
                        if not is_input:
                                log("Unexpected replacement macro " + macro + " encountered.", 6)
 
                        if not is_input:
                                log("Unexpected replacement macro " + macro + " encountered.", 6)
 
+               # and now we act on the replacement
+               text = text.replace("$(" + macro + ")", replacement)
+
        # replace the look-like-a-macro sequence
        text = text.replace("$_(", "$(")
 
        # replace the look-like-a-macro sequence
        text = text.replace("$_(", "$(")
 
@@ -1410,6 +1473,9 @@ def handle_user_input(user):
        # since we got input, flag that the menu/prompt needs to be redisplayed
        user.menu_seen = False
 
        # since we got input, flag that the menu/prompt needs to be redisplayed
        user.menu_seen = False
 
+       # update the last_input timestamp while we're at it
+       user.last_input = universe.get_time()
+
 def generic_menu_handler(user):
        """A generic menu choice handler."""
 
 def generic_menu_handler(user):
        """A generic menu choice handler."""