nakedmud-mod/
nakedmud-mod/html/tutorials/
nakedmud-mod/html/tutorials/building_extras/
nakedmud-mod/html/tutorials/c/
nakedmud-mod/html/tutorials/reference/
nakedmud-mod/html/tutorials/scripting/
nakedmud-mod/html/tutorials/scripting_extras/
nakedmud-mod/lib/
nakedmud-mod/lib/help/A/
nakedmud-mod/lib/help/B/
nakedmud-mod/lib/help/C/
nakedmud-mod/lib/help/D/
nakedmud-mod/lib/help/G/
nakedmud-mod/lib/help/H/
nakedmud-mod/lib/help/J/
nakedmud-mod/lib/help/L/
nakedmud-mod/lib/help/M/
nakedmud-mod/lib/help/O/
nakedmud-mod/lib/help/P/
nakedmud-mod/lib/help/R/
nakedmud-mod/lib/help/S/
nakedmud-mod/lib/help/W/
nakedmud-mod/lib/logs/
nakedmud-mod/lib/misc/
nakedmud-mod/lib/players/
nakedmud-mod/lib/pymodules/polc/
nakedmud-mod/lib/txt/
nakedmud-mod/lib/world/
nakedmud-mod/lib/world/zones/examples/
nakedmud-mod/lib/world/zones/examples/mproto/
nakedmud-mod/lib/world/zones/examples/oproto/
nakedmud-mod/lib/world/zones/examples/reset/
nakedmud-mod/lib/world/zones/examples/rproto/
nakedmud-mod/lib/world/zones/examples/trigger/
nakedmud-mod/lib/world/zones/limbo/
nakedmud-mod/lib/world/zones/limbo/room/
nakedmud-mod/lib/world/zones/limbo/rproto/
nakedmud-mod/src/alias/
nakedmud-mod/src/dyn_vars/
nakedmud-mod/src/editor/
nakedmud-mod/src/example_module/
nakedmud-mod/src/help2/
nakedmud-mod/src/set_val/
nakedmud-mod/src/socials/
nakedmud-mod/src/time/
"""
advanced_telopt.py

Adds advanced TELOPT negotiations to the MUD, including support for:

* TELOPT_NAWS  - Negotiate About Window Size
    Determine the size of the client window in columns and rows.

* TELOPT_TTYPE - Terminal Type
    Determine the connected client to estimate its capabilities.

* TELOPT_EOR   - End-of-Record
    Mark the end of text transmission from the server with IAC EOR to let a
    client know the exact point. If enabled, this is automatically appended
    to the end of the prompt.

* TELOPT_MSP   - MUD Sound Protocol
    Trigger the playback of sounds and music on the client. Only basic support
    is included... negotiation and the triggering of hooks when it's enabled
    or disabled. The rest of the implementation is up to the MUD creator, though
    it consists merely of sending your own MSP commands to the client.
    
* TELOPT_MSDP  - MUD Server Data Protocol
    MSDP allows you to easilly send variables to the client.

* TELOPT_MXP   - MUD eXtension Protocol
    MXP is, for those who don't know, an application of HTML-like tags to the
    MUD itself, allowing for richer interaction with the user. It is in no way
    critical to playing and enjoying the game, but it is a nice touch.
    
    Note that this implementation of MXP isn't complete, as it doesn't handle
    asking the client which tags it supports. I'd recommend keeping yourself
    limited to simple tags like <SEND> if you don't add <SUPPORTS> support.
    
    For more information about MXP, visit http://www.zuggsoft.com/zmud/mxp.htm

* Window Title Text
    Set a custom string to the window's title bar. This also works together
    with TELOPT_TTYPE to avoid sending the code to clients that wouldn't
    properly interpret it.
    
    If the bell() function of a socket is called, the title will be flashed
    with two prepended asterisks until the next command is recieved. This can
    be used to notify a distracted player when the MUD needs their input.
    
* XTERM 256 and ANSI Colors
    This file also handles color code parsing, taking that functionality away
    from colour.py as to combine color formatting and MXP conversion into one
    post-processing function. I haven't tested it, but I can only assume that
    one less function call makes things easier on the server.
"""
import mud
import mudsys, mudsock, storage, auxiliary, hooks, telnetlib, re, event, random

################################################################################
# variables
################################################################################

# Various Regex Expressions
coloreg = re.compile('{([ndrgybpcw{]|[0-5][0-5][0-5]|6[0-1]\d|62[0-3]|7)', re.IGNORECASE)
mxpreg  = re.compile('[<>&]')
linkreg = re.compile("""(?<!=['"])((?:https?|telnet)://(?:\w+(?:\.-\w*)+|(?:[a-z0-9\-]*(?:[a-z0-9\-])\.)+(?:com|edu|biz|gov|in(?:t|fo)|mil|net|org|[a-zA-Z]\.[a-z][a-z]))(?::\d{1,4})?(?:/[^.!,?;"\'<>()\[\]\{\}\s\x1b\x7F-\xFF]*(?:[.!,?]+[^.!,?;"\'<>()\[\]\{\}\s\x1b\x7F-\xFF]+)*)?)""",re.IGNORECASE)

# List of all the letter colors.
colorletters = 'drgybpcwDRGYBPCW'

# ANSI Color Replacements
extcolors = ['0;30', '0;31', '0;32', '0;33', '0;34', '0;35', '0;36', '0;37',
'1;30', '1;31', '1;32', '1;33', '1;34', '1;35', '1;36', '1;37', '0;30', '0;34',
'0;34', '0;34', '1;34', '1;34', '0;32', '0;36', '0;36', '0;36', '1;34', '1;34',
'0;32', '0;36', '0;36', '0;36', '1;36', '1;36', '0;32', '0;36', '0;36', '0;36',
'1;36', '1;36', '1;32', '1;32', '1;36', '1;36', '1;36', '1;36', '1;32', '1;32',
'1;36', '1;36', '1;36', '1;36', '0;31', '0;35', '0;35', '0;35', '1;34', '1;34',
'0;33', '1;30', '0;34', '0;34', '1;34', '1;34', '0;33', '0;32', '0;36', '0;36',
'1;34', '1;34', '0;33', '0;32', '0;36', '0;36', '1;36', '1;36', '1;32', '1;32',
'1;32', '1;36', '1;36', '1;36', '1;32', '1;32', '1;32', '1;36', '1;36', '1;36',
'0;31', '0;35', '0;35', '0;35', '1;35', '1;35', '0;33', '0;31', '0;35', '0;35',
'1;34', '1;34', '0;33', '0;33', '0;37', '0;37', '1;34', '1;34', '0;33', '0;33',
'0;37', '0;37', '1;36', '1;34', '1;33', '1;32', '1;32', '1;36', '1;36', '1;36',
'1;33', '1;32', '1;32', '1;32', '1;36', '1;36', '0;31', '0;35', '0;35', '0;35',
'1;35', '1;35', '0;33', '0;31', '0;35', '0;35', '1;35', '1;35', '0;33', '0;33',
'0;37', '0;37', '1;35', '1;34', '0;33', '0;33', '0;37', '0;37', '1;37', '1;34',
'1;33', '1;33', '1;33', '1;37', '1;37', '1;36', '1;33', '1;33', '1;32', '1;32',
'1;36', '1;36', '1;31', '1;31', '1;35', '1;35', '1;35', '1;35', '1;31', '1;31',
'1;31', '1;35', '1;35', '1;35', '1;33', '1;31', '1;31', '1;35', '1;35', '1;35',
'1;33', '1;33', '1;33', '1;37', '1;37', '1;35', '1;33', '1;33', '1;33', '1;37',
'1;37', '1;37', '1;33', '1;33', '1;33', '1;33', '1;37', '1;37', '1;31', '1;31',
'1;35', '1;35', '1;35', '1;35', '1;31', '1;31', '1;31', '1;35', '1;35', '1;35',
'1;33', '1;31', '1;31', '1;31', '1;35', '1;35', '1;33', '1;33', '1;31', '1;31',
'1;35', '1;35', '1;33', '1;33', '1;33', '1;33', '1;37', '1;37', '1;33', '1;33',
'1;33', '1;33', '1;37', '1;37', '0;30', '0;30', '1;30', '1;30', '1;30', '1;30',
'1;30', '1;30', '1;30', '1;30', '1;30', '1;30', '1;30', '0;37', '0;37', '0;37',
'0;37', '0;37', '0;37', '1;37', '1;37', '1;37', '1;37', '1;37']

htmlcolors = ['#000000', '#800000', '#008000', '#808000', '#000080', '#800080',
'#008080', '#C0C0C0', '#808080', '#FF0000', '#00FF00', '#FFFF00', '#0000FF',
'#FF00FF', '#00FFFF', '#FFFFFF', '#000000', '#00005f', '#000087', '#0000af',
'#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7',
'#005fff', '#008700', '#00875f', '#008787', '#0087af', '#0087d7', '#0087ff',
'#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700',
'#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f',
'#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087',
'#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf',
'#5f5fd7', '#5f5fff', '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7',
'#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff',
'#5fd700', '#5fd75f', '#5fd787', '#5fd7af', '#5fd7d7', '#5fd7ff', '#5fff00',
'#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f',
'#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f', '#875f87',
'#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af',
'#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7',
'#87afff', '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff',
'#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000',
'#af005f', '#af0087', '#af00af', '#af00d7', '#af00ff', '#af5f00', '#af5f5f',
'#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787',
'#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f', '#afaf87', '#afafaf',
'#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7',
'#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff',
'#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00',
'#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f',
'#d78787', '#d787af', '#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87',
'#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af',
'#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f', '#d7ff87', '#d7ffaf', '#d7ffd7',
'#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff',
'#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff', '#ff8700',
'#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f',
'#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787',
'#ffd7af', '#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf',
'#ffffd7', '#ffffff', '#191919', '#232323', '#2d2d2d', '#373737', '#414141',
'#4b4b4b', '#555555', '#5f5f5f', '#696969', '#737373', '#7d7d7d', '#878787',
'#919191', '#9b9b9b', '#a5a5a5', '#afafaf', '#b9b9b9', '#c3c3c3', '#cdcdcd',
'#d7d7d7', '#e1e1e1', '#ebebeb', '#f5f5f5', '#ffffff']

# Values of the 6x6x6 Cube
colorvalues = [0, 95, 135, 175, 215, 255]

# ANSI Color Codes
colors = {'n' : '0',
          'd' : '30',
          'r' : '31',
          'g' : '32',
          'y' : '33',
          'b' : '34',
          'p' : '35',
          'c' : '36',
          'w' : '37'}

# The file for copyover data copying
copyover_file = "../lib/misc/copyover"

# TELOPT Codes
MSDP    = 'E'    # "%c" % 69
MSP     = 'Z'    # "%c" % 90
MXP     = '['     # "%c" % 91

# TELOPT_MSP:   Handshake Strings
server_will_msp    = telnetlib.IAC + telnetlib.WILL + MSP
server_wont_msp    = telnetlib.IAC + telnetlib.WONT + MSP
client_do_msp      = telnetlib.IAC + telnetlib.DO   + MSP
client_dont_msp    = telnetlib.IAC + telnetlib.DONT + MSP

# TELOPT_MXP:   Handshake Strings
server_will_mxp    = telnetlib.IAC + telnetlib.WILL + MXP
server_wont_mxp    = telnetlib.IAC + telnetlib.WONT + MXP
client_do_mxp      = telnetlib.IAC + telnetlib.DO   + MXP
client_dont_mxp    = telnetlib.IAC + telnetlib.DONT + MXP
server_start_mxp   = telnetlib.IAC + telnetlib.SB   + MXP + telnetlib.IAC + telnetlib.SE

# TELOPT_NAWS:  Handshake Strings
server_do_naws     = telnetlib.IAC + telnetlib.DO   + telnetlib.NAWS
client_will_naws   = telnetlib.IAC + telnetlib.WILL + telnetlib.NAWS
client_naws        = telnetlib.IAC + telnetlib.SB   + telnetlib.NAWS

# TELOPT_TTYPE: Handshake Strings
server_do_ttype    = telnetlib.IAC + telnetlib.DO   + telnetlib.TTYPE
client_will_ttype  = telnetlib.IAC + telnetlib.WILL + telnetlib.TTYPE
server_send_ttype  = telnetlib.IAC + telnetlib.SB   + telnetlib.TTYPE + telnetlib.ECHO + telnetlib.IAC + telnetlib.SE # ECHO = SEND
client_ttype_is    = telnetlib.IAC + telnetlib.SB   + telnetlib.TTYPE

# TELOPT_EOR:   Handshake Strings
server_will_eor    = telnetlib.IAC + telnetlib.WILL + telnetlib.EOR
server_wont_eor    = telnetlib.IAC + telnetlib.WONT + telnetlib.EOR
client_do_eor      = telnetlib.IAC + telnetlib.DO   + telnetlib.EOR
client_dont_eor    = telnetlib.IAC + telnetlib.DONT + telnetlib.EOR
server_eor_code    = telnetlib.IAC + '\xEF'

# TELOPT_MSDP:  Handshake Strings
server_will_msdp   = telnetlib.IAC + telnetlib.WILL + MSDP
client_do_msdp     = telnetlib.IAC + telnetlib.DO   + MSDP
client_dont_msdp   = telnetlib.IAC + telnetlib.DONT + MSDP
msdp_start         = telnetlib.IAC + telnetlib.SB   + MSDP
msdp_end           = telnetlib.IAC + telnetlib.SE

# MSDP Strings
MSDP_VAR = '\x01' # "%c" % 1
MSDP_VAL = '\x02' # "%c" % 2

# MXP Line Tags
mxp_escape         = '\x1b['
mxp_open_line      = mxp_escape + '0z'
mxp_secure_line    = mxp_escape + '1z'
mxp_locked_line    = mxp_escape + '2z'
mxp_reset          = mxp_escape + '3z'
mxp_temp_secure    = mxp_escape + '4z'
mxp_lock_open      = mxp_escape + '5z'
mxp_lock_secure    = mxp_escape + '6z'
mxp_lock_locked    = mxp_escape + '7z'

# MXP Tag Markers
mxp_tag_start      = '\x01MXP\x03'
mxp_tag_end        = '\x02MXP\x03'
mxp_and            = '\x04MXP\x03'

# MXP Secure Tags
mxp_secure_tags = ['SEND','A','VAR','NOBR','P','BR','SBR','EXPIRE','VERSION','SUPPORT','GAUGE','STAT','SOUND','MUSIC','FRAME','DEST','RELOCATE','IMAGE','FILTER']

# MXP Tags
mxp_tags = []

# Known clients.
clients  = ['jmud','xterm','fmud','zmud','mushclient','tintin++','ansi','unknown']

# Known clients without proper title support
no_title = ['fmud','zmud','mushclient','ansi','unknown']

# Known clients with proper xterm 256 colors support
clients256 = ['jmud','zmud','mushclient','tintin++','xterm']

################################################################################
# Auxiliary Data for the Socket
################################################################################
class VariableData:
  def __init__(self):
    self.pairs = {}
  
  def __getitem__(self, key):
    return self.pairs[key]
  
  def __setitem__(self, key, val):
    self.pairs[key] = val
  
  def __delitem__(self, key):
    del self.pairs[key]
  
  def has_key(self, key):
    return self.pairs.has_key(key)
  
  def copy(self):
    return self.pairs.copy()
  
  def __str__(self):
    return str(self.pairs)

class TeloptAux:
  def __init__(self, set = None):
    self.nMXP    = False
    self.MXP     = 0
    self.mxp_sec = False
    
    self.nMSP    = False
    self.MSP     = 0
    
    self.nMSDP   = False
    self.MSDP    = 0
    self.vMSDP   = {}
    
    self.title   = 0
    self.ntitle  = False
    self.title_s = ''
    self.bell    = False
    self.bellon  = False
    
    self.cols    = 80
    self.rows    = 24
    self.client  = 'unknown'
    self.eor     = False
    self.n256    = False
    self.c256    = 0
    if set != None:
      self.load(set)
  
  def copy(self):
    newdata = TeloptAux()
    self.copyTo(newdata)
    return newdata
  
  def copyTo(self, to):
    to.nMXP    = self.nMXP
    to.MXP     = self.MXP
    to.mxp_sec = self.mxp_sec
    
    to.nMSP    = self.nMSP
    to.MSP     = self.MSP
    
    to.nMSDP   = self.nMSDP
    to.MSDP    = self.MSDP
    to.vMSDP   = self.vMSDP.copy()
    
    to.title   = self.title
    to.ntitle  = self.ntitle
    to.title_s = self.title_s
    to.bell    = self.bell
    to.bellon  = self.bellon

    to.cols    = self.cols
    to.rows    = self.rows
    to.client  = self.client
    to.eor     = self.eor
    to.n256    = self.n256
    to.c256    = self.c256
    
  def load(self, set = None):
    if set is not None:
      self.nMXP    = set.readBool("nMXP")
      self.MXP     = set.readInt("MXP")
      self.mxp_sec = set.readBool("mxp_sec")
      
      self.nMSP    = set.readBool("nMSP")
      self.MSP     = set.readInt("MSP")
      
      self.nMSDP   = set.readBool("nMSDP")
      self.MSDP    = set.readInt("MSDP")
      
      self.title   = set.readInt("title")
      self.ntitle  = set.readBool("ntitle")
      self.title_s = set.readString("title_s")
      self.bell    = set.readBool("bell")
      self.bellon  = set.readBool("bellon")
      
      self.cols    = set.readInt("cols")
      self.rows    = set.readInt("rows")
      self.client  = set.readString("client")
      self.eor     = set.readBool("eor")
      self.n256    = set.readBool("n256")
      self.c256    = set.readInt("c256")
  
  def store(self):
    set = storage.StorageSet()
    set.storeBool("nMXP",      self.nMXP)
    set.storeInt( "MXP",       self.MXP)
    set.storeBool("mxp_sec",   self.mxp_sec)
    
    set.storeBool("nMSP",      self.nMSP)
    set.storeInt( "MSP",       self.MSP)
    
    set.storeBool("nMSDP",     self.nMSDP)
    set.storeInt( "MSDP",      self.MSDP)
    
    set.storeInt( "title",     self.title)
    set.storeBool("ntitle",    self.ntitle)
    set.storeString("title_s", self.title_s)
    set.storeBool("bell",      self.bell)
    set.storeBool("bellon",    self.bellon)
    
    set.storeInt( "cols",      self.cols)
    set.storeInt( "rows",      self.rows)
    set.storeString("client",  self.client)
    set.storeBool("eor",       self.eor)
    set.storeBool("n256",      self.n256)
    set.storeInt( "c256",      self.c256)
    return set

################################################################################
# Functions
################################################################################
def register_mxp_flag(name, flag):
  '''Add a new MXP flag to the list of tags. This is good for easilly setting a list of
     variables for things like the prompt, or adding custom tags for flagging room
     names and descriptions and the like.'''
  register_mxp_tag(name, 'FLAG="'+flag+'"', True)
  
def register_mxp_tag(name, args = None, secure = False):
  '''Add a new MXP tag to the list of tags. The name must be an alphanumeric string, and
     args should be a string of the element's arguments, though it could be None. If secure
     is true, the tag will be added to a list of flags that are sent via an MXP secure line.'''
  if type(name) != str or not name.isalnum(): raise TypeError("Name must be an alphanumeric string.")
  args = mxpreg.sub(lambda m: [[mxp_tag_start,mxp_tag_end][m.group() == '>'],'&'][m.group() == '&'], args)
  mxp_tags.append("%s!ELEMENT %s%s%s" % (mxp_tag_start, name, ['',' '+str(args)][len(str(args)) > 0], mxp_tag_end))
  if secure:
    mxp_secure_tags.append(name)

def mxp_tag(sock, text = '', tag = '', args = '', closing = False):
  '''Output a MXP tag. If it's a secure tag, flag the socket so it's next line is sent secure.
  
     If closing is true and text is not supplied, only a closing tag will be returned. Be sure that args is None if you're making a closing tag.
     If text is None, only a single tag will be created. This can be useful for sending things like the <IMAGE> tag.'''
  if not sock: return text
  if text is not None: text = str(text)
  if tag and tag in mxp_secure_tags:  sock.telopt.mxp_sec = True
  if not sock.MXP or not tag: return text
  elif text and args:         return '%s%s %s%s%s%s/%s%s' % (mxp_tag_start,tag,args,mxp_tag_end,text,mxp_tag_start,tag,mxp_tag_end)
  elif text and not args:     return '%s%s%s%s%s/%s%s' % (mxp_tag_start,tag,mxp_tag_end,text,mxp_tag_start,tag,mxp_tag_end)
  elif not text and args:     return '%s%s%s %s%s' % (mxp_tag_start,closing and '/' or '', tag, args, mxp_tag_end)
  elif not text and not args: return '%s%s%s%s' % (mxp_tag_start, closing and '/' or '', tag, mxp_tag_end)
  else: return text

def copyover():
  '''Saves the telopt auxiliary data of all active connections with set players
     to be restored after the copyover is complete.'''
  set  = storage.StorageSet()
  list = storage.StorageList()
  
  # Iterate through the sockets and save the ones with connected users. Ignore
  # the others since they're simply disconnected.
  for sk in mudsock.socket_list():
    if sk.ch:
      s = storage.StorageSet()
      s.storeString("k", sk.ch.name)
      s.storeSet("v", sk.telopt.store())
      list.add(s)
  
  # Store and close the set
  set.storeList("list", list)
  set.write(copyover_file)
  set.close()
  
def send_title(sock):
  '''Sends the current title string to the socket.'''
  sock.trans_raw('\33]2;' + (sock.telopt.bellon and '**' or '') + sock.telopt.title_s + '\33\\')
  
################################################################################
# Colour Conversion Functions
# Since colors are hard-coded now, these functions are just included to show
# you how it was done in case you want to play with it.
################################################################################
def rgb2hsv(r, g, b):
  '''Convert an RGB color to HSV.'''
  r,g,b = r / 255.0, g / 255.0, b / 255.0
  
  mn = min(r,g,b) # Minimum Value
  mx = max(r,g,b) # Maximum Value
  dm = mx - mn    # Delta Value
  v = mx
  
  if dm == 0: return 0,0,v # This is a gray, no chroma.
  else: s = dm / mx
    
  dr = ((( mx - r ) / 6) + (dm / 2) ) / dm
  dg = ((( mx - g ) / 6) + (dm / 2) ) / dm
  db = ((( mx - b ) / 6) + (dm / 2) ) / dm
    
  if   r == mx: h = db - dg
  elif g == mx: h = (1/3.0) + dr - db
  elif b == mx: h = (2/3.0) + dg - dr
  if h < 0: h += 1
  if h > 1: h -= 1
  return h,s,v

def simplecolor(c, html = False):
  '''Simplify an xterm 256 color code down to ANSI 16. It isn't perfect,
     but it works a whole lot better than stripping all color, or letting
     people with poor client choices from seeing a screen of garbled mess.
     
     Will return an HTML hex color code if html is true'''
  if c < 16:
    # This is one of the 16 ANSI colors
    cs = 'drgybpcw'
    if html:
      return '#'+['000000','800000','008000','808000','000080','800080','008080','C0C0C0','808080','FF0000','00FF00','FFFF00','0000FF','FF00FF','00FFFF','FFFFFF'][c]
    else:
      if c > 7: return '1;'+colors[cs[c-8]]
      else:     return '0;'+colors[cs[c]]
  elif c > 231:
    # Grayscale
    if html:
      v = min(255,15 + ((c - 231) * 10))
      return '#%02x%02x%02x' % (v,v,v)
    else:
      if   c > 250: return '1;'+colors['w']
      elif c > 244: return '0;'+colors['w']
      elif c > 233: return '1;'+colors['d']
      else:         return '0;'+colors['d']
  else:
    # From the 6x6x6
    if html:
      return '#%02x%02x%02x' % (colorvalues[((c-16)/36)%6], colorvalues[((c-16)/6)%6], colorvalues[(c-16)%6])
    
    h,s,v = rgb2hsv(colorvalues[((c-16)/36)%6], colorvalues[((c-16)/6)%6], colorvalues[(c-16)%6])
    
    # If the saturation is very low, treat it as a gray.
    if s < 0.25:
      if   v > 0.75: return '1;'+colors['w']
      elif v > 0.50: return '0;'+colors['w']
      elif v > 0.25: return '1;'+colors['d']
      else:          return '0;'+colors['d']
    
    if   h < 3/36.0 or h > 33/36.0:
      # Red
      if v > 0.75: return '1;'+colors['r']
      else:        return '0;'+colors['r']
    elif h > 3/36.0 and h < 9/36.0:
      # Yellow
      if v > 0.75: return '1;'+colors['y']
      else:        return '0;'+colors['y']
    elif h > 9/36.0 and h < 15/36.0:
      # Green
      if v > 0.75: return '1;'+colors['g']
      else:        return '0;'+colors['g']
    elif h > 15/36.0 and h < 21/36.0:
      # Cyan
      if v > 0.75: return '1;'+colors['c']
      else:        return '0;'+colors['c']
    elif h > 21/36.0 and h < 27/36.0:
      # Blue
      if v > 0.75: return '1;'+colors['b']
      else:        return '0;'+colors['b']
    elif h > 27/36.0 and h < 33/36.0:
      # Purple
      if v > 0.75: return '1;'+colors['p']
      else:        return '0;'+colors['p']
    else:
      # Wha...?
      return '0;'+colors['d']

################################################################################
# Events and Hooks
################################################################################
def flash_title(owner, data, arg):
  '''Scheduled event that, once per second, toggles the titlebar notification flag and sends the updated title to the client.'''
  if owner.sock.telopt.bell:
    owner.sock.telopt.bellon = not owner.sock.telopt.bellon
    send_title(owner.sock)
    event.start_event(owner, 1, flash_title)

def post_command(info):
  '''Hook that will stop flashing the titlebar once a command is recieved.'''
  ch, cmd, arg = hooks.parse_info(info)
  if ch.sock is not None and ch.sock.telopt.bell:
    ch.sock.telopt.bell = False
    ch.sock.telopt.bellon = False
    send_title(ch.sock)

def begin_negotiations(info):
  '''Sends the initial negotiation strings to new sockets.'''
  sock, = hooks.parse_info(info)
  sock.trans_raw(server_do_ttype + server_do_naws + server_will_eor + server_will_msp + server_will_mxp + server_will_msdp)
  
def process_iac_sequence(info):
  '''Process the IAC sequence to see if it was one of the options supported.'''
  sock, cmd = hooks.parse_info(info)
  
  # TELOPT_TTYPE
  if   cmd == client_will_ttype:
    # The client has TTYPE. Request a TTYPE.
    sock.trans_raw(server_send_ttype)
  elif cmd.startswith(client_ttype_is):
    # TTYPE Response
    client = cmd[3:-3].lower()
    sock.telopt.client = client
    # 256-colors support
    sock.telopt.n256 = client in clients256
    
    # Window Title Setting
    sock.telopt.ntitle = not client in no_title
    if sock.telopt.ntitle and sock.telopt.title == 0: send_title(sock)
  
  # TELOPT_NAWS
  # This handler is a bit messed up since any '\0' characters are removed from the string.
  elif cmd.startswith(client_naws):
    if   len(cmd) == 8:  # Two numbers. Cols x Rows
      cols, rows = ord(cmd[3]), ord(cmd[4])
    elif len(cmd) == 9:  # Three Numbers. Assume Cols Cols x Rows
      cols, rows = ord(cmd[3]) * 256 + ord(cmd[4]), ord(cmd[5])
    elif len(cmd) == 10: # Four numbers. Cols Cols x Rows Rows
      cols, rows = ord(cmd[3]) * 256 + ord(cmd[4]), ord(cmd[5]) * 256 + ord(cmd[6])
    else: # What? Just use the default then
      cols, rows = 80, 23
    # If cols is less than five, assume it was supposed to be the larger byte.
    if cols < 5: cols *= 256
    #sock.telopt.cols,sock.telopt.rows = cols, rows
    #sock.page_cols,sock.page_rows = cols, rows
    sock.cols,sock.rows = cols, rows
  
  # TELOPT_EOR
  elif cmd == client_do_eor:
    sock.telopt.eor = True
  elif cmd == client_dont_eor:
    sock.telopt.eor = False
  
  # TELOPT_MSDP
  elif cmd == client_do_msdp:
    # Enables the nMSDP tag. If MSDP is enabled because of this, run a hook.
    old = sock.nMSDP
    sock.telopt.nMSDP = True
    if not old and sock.telopt.MSDP == 0: hooks.run("msdp_enabled", hooks.build_info("sk",(sock,)))
  elif cmd == client_dont_msdp:
    # Disables the nMSDP tag. If MSDP is disabled after this, run a hook.
    old = sock.nMSDP
    sock.telopt.nMSDP = False
    if old and sock.telopt.MSDP == 0: hooks.run("msdp_disabled", hooks.build_info("sk",(sock,)))
  
  # TELOPT_MSP
  elif cmd == client_do_msp:
    # Enables the nMSP tag. If MSP is enabled because of this, run a hook.
    old = sock.nMSP
    sock.telopt.nMSP = True
    if not old and sock.telopt.MSP == 0: hooks.run("msp_enabled", hooks.build_info("sk",(sock,)))
  elif cmd == client_dont_msp:
    # Disables the nMSP tag. If MSP is disabled after this, run a hook.
    old = sock.nMSP
    sock.telopt.nMSP = False
    if old and sock.telopt.MSP == 0: hooks.run("msp_disabled", hooks.build_info("sk",(sock,)))
  
  # TELOPT_MXP
  elif cmd == client_do_mxp:
    # Enables the nMXP tag. If MXP is enabled because of this, run a hook.
    old = sock.nMXP
    sock.telopt.nMXP = True
    if not old and sock.telopt.MXP == 0: hooks.run("mxp_enabled", hooks.build_info("sk",(sock,)))
  elif cmd == client_dont_mxp:
    # Disables the nMXP tag. If MXP is disabled because of this, run a hook.
    old = sock.nMXP
    sock.telopt.nMXP = False
    if old and sock.telopt.MXP == 0: hooks.run("mxp_disabled", hooks.build_info("sk",(sock,)))

def mxp_stop_hook(info):
  '''Send a wont MXP string.'''
  sock, = hooks.parse_info(info)
  sock.trans_raw(server_wont_mxp)

def mxp_start_hook(info):
  '''Send out our notice that MXP is starting, along with the MXP tags.'''
  sock, = hooks.parse_info(info)
  sock.trans_raw(server_start_mxp + mxp_lock_secure + ''.join(mxp_tags))

def msdp_start_hook(info):
  '''Send out all the current MSDP variables.'''
  sock, = hooks.parse_info(info)

  # If there's no variables, return.
  if len(sock.telopt.vMSDP) == 0: return

  # Build a string.
  buf = msdp_start
  for k,v in sock.telopt.vMSDP.iteritems():
    buf += MSDP_VAR + k + __outmsdpv(v)
    
  # Send the string
  sock.trans_raw(buf + msdp_end)

def copyover_complete(info):
  '''Loads socket auxiliary data for the active connections from a file.'''
  set = storage.StorageSet(copyover_file)
  
  # Read the data into a dict.
  data = { }
  for s in set.readList("list").sets():
    data[s.readString("k")] = s.readSet("v")
  
  # Iterate through sockets, and if a player is connected, restore the aux data
  # To their socket.
  for sk in mudsock.socket_list():
    if sk.ch is not None and data.has_key(sk.ch.name):
      sk.telopt.load(data[sk.ch.name])
      # If MXP is enabled, resend tags in case they were updated.
      if sk.MXP: mxp_start_hook(hooks.build_info("sk",(sk,)))
  
  # Close the set
  set.close()

def process_outbound_prompt(info):
  '''Process outbound prompt to add END-OF-RECORD on the end if necessary.'''
  sock, = hooks.parse_info(info)
  # First, process the text normally.
  process_outbound_text(info)
  if sock.endofrecord:
    sock.outbound_text += server_eor_code

def process_outbound_text(info):
  '''Process outbound text by replacing color codes and escaping MXP tags.'''
  sock, = hooks.parse_info(info)
  buf = sock.outbound_text
  
  # Process color sequences
  if sock.c256:
    if sock.client == 'zmud':
      # MXP for zMUD
      buf = buf.replace("\n",mxp_tag_start+'/C'+mxp_tag_end+'\n'+mxp_tag_start+'C "#C0C0C0"'+mxp_tag_end)
      buf = mxp_tag_start+'C "#C0C0C0"'+mxp_tag_end+coloreg.sub(mxp_color, buf)+mxp_tag_start+'/C'+mxp_tag_end
    else:
      buf = coloreg.sub(c256_color, buf)
  else:
    buf = coloreg.sub(ansi_color, buf)
  
  # Is MXP enabled?
  if sock.MXP:
    # It is.
    buf = mxpreg.sub(lambda m: ['&gt;','&lt;','&amp;'][int(m.group() != '>') + int(m.group() == '&')], buf)
    buf = buf.replace(mxp_tag_start, '<').replace(mxp_tag_end, '>').replace(mxp_and, '&')
    buf,c = linkreg.subn(lambda m: '<A href="'+m.group(1)+'">'+m.group(1)+'</A>', buf)
    if c > 0: sock.telopt.mxp_sec = True
  elif sock.client == 'zmud':
    # If it's zmud, we have to escape tags regardless. It doesn't negotiate well with others.
    buf = mxpreg.sub(lambda m: ['&gt;','&lt;'][m.group() == '<'], buf)
  
  # Is it a secure line? If so, prepend our secure line tag.
  if sock.telopt.mxp_sec:
    buf = mxp_lock_secure + buf + mxp_lock_open
    sock.telopt.mxp_sec = False
  
  # Return our processed text.
  sock.outbound_text = buf

def mxp_color(match):
  '''Replace a matching color code with an MXP tag. This is for zMUD.'''
  m = match.group(1)
  if m == '{': return '{'
  if m == 'n' or m == 'N': return mxp_tag_start+'/C'+mxp_tag_end+mxp_tag_start+'C "#C0C0C0"'+mxp_tag_end
  if m in colorletters: return mxp_tag_start+'/C'+mxp_tag_end+mxp_tag_start+'C "'+htmlcolors[colorletters.find(m)]+'"'+mxp_tag_end
  # Random
  if m[0] == '7': return mxp_tag_start+'/C'+mxp_tag_end+mxp_tag_start+'C "'+htmlcolors[random.randrange(0,255)]+'"'+mxp_tag_end
  # Still here? One of the extended codes then. First, check for grayscale.
  if m[0] == '6': return mxp_tag_start+'/C'+mxp_tag_end+mxp_tag_start+'C "'+htmlcolors[232 + (int(m[1:]) % 24)]+'"'+mxp_tag_end
  # One of the 6x6x6
  return mxp_tag_start+'/C'+mxp_tag_end+mxp_tag_start+'C "'+htmlcolors[16 + int(m[0])*36 + int(m[1])*6 + int(m[2])]+'"'+mxp_tag_end

def c256_color(match):
  '''Replace a matching color code with the proper 256-color escape sequence.'''
  m = match.group(1)
  if m == '{': return '{'
  if m == 'n' or m == 'N': return '\x1b[0;'+colors['n']+'m'
  if m in colorletters: return '\x1b[38;5;%dm' % colorletters.find(m)
  # Random
  if m[0] == '7': return '\x1b[38;5;%dm' % random.randrange(0,255)
  # Still here? One of the extended codes then. First, check for grayscale.
  if m[0] == '6': return '\x1b[38;5;%dm' % (232 + int(m[1:]) % 24)
  # One of the 6x6x6.
  return '\x1b[38;5;%dm' % (16 + int(m[0])*36 + int(m[1])*6 + int(m[2]))

def ansi_color(match):
  '''Replace a matching color code with the simplified ANSI escape sequence.'''
  m = match.group(1)
  if m == '{': return '{'
  if m == 'n' or m == 'N': return '\x1b[0;'+colors['n']+'m'
  if m in colorletters: return '\x1b['+extcolors[colorletters.find(m)]+'m'
  # Random
  if m[0] == '7': return '\x1b['+extcolors[random.randrange(0,255)]+'m'
  # Still here? One of the extended codes then. First, check for grayscale.
  if m[0] == '6': return '\x1b['+extcolors[232 + int(m[1:]) % 24]+'m'
  # One of the 6x6x6
  return '\x1b['+extcolors[16 + int(m[0])*36 + int(m[1]) * 6 + int(m[2])]+'m'

################################################################################
# Properties
################################################################################
# The Aux Data

def __settelopt(s, data):
  if not isinstance(data, TeloptAux): raise TypeError("TeloptAux object not supplied")
  data.copyTo(s.aux("telopt"))
def __gettelopt(s):
  return s.aux("telopt")

# Settable Properties

def __delmxp(s):      s.MXP = 0
def __getmxp(s):      return [False, s.telopt.nMXP, True][s.telopt.MXP + 1]
def __setmxp(s, v):
  old = s.MXP # Run the appropriate hook if MXP is enabled or disabled here.
  if type(v) == int and -2 < v < 2: s.telopt.MXP = v
  else: s.telopt.MXP = bool(v) and 1 or -1
  if old and not s.MXP: hooks.run("mxp_disabled", hooks.build_info("sk",(s,)))
  elif not old and s.MXP: hooks.run("mxp_enabled", hooks.build_info("sk",(s,)))

def __delmsp(s):      s.MSP = 0
def __getmsp(s):      return [False, s.telopt.nMSP, True][s.telopt.MSP + 1]
def __setmsp(s, v):
  old = s.MSP # Run the appropriate hook if MSP is enabled or disabled here.
  if type(v) == int and -2 < v < 2: s.telopt.MSP = v
  else: s.telopt.MSP = bool(v) and 1 or -1
  if old and not s.MSP: hooks.run("msp_disabled", hooks.build_info("sk",(s,)))
  elif not old and s.MSP: hooks.run("msp_enabled", hooks.build_info("sk",(s,)))

def __deltitle(s):    s.title = 0
def __gettitle(s):    return [False, s.telopt.ntitle, True][s.telopt.title + 1]
def __settitle(s,v):
  old,olds = s.title, s.telopt.title_s
  # Depending on the variable type, either store the flag or the string.
  if   type(v) == int and -2 < v < 2: s.telopt.title = v
  elif type(v) == str: s.telopt.title_s = v
  else: s.telopt.title = bool(v) and 1 or -1
  
  # If we've updated, send the fresh title.
  if s.title and (not old or olds is not s.telopt.title_s): send_title(s)
  
def __delc256(s): s.telopt.c256 = 0
def __getc256(s): return [False, s.telopt.n256, True][s.telopt.c256 + 1]
def __setc256(s,v):
  if type(v) == int and -2 < v < 2: s.telopt.c256 = v
  else: s.telopt.c256 = bool(v) and 1 or -1

def __delmsdp(s):     s.MSDP = 0
def __getmsdp(s):     return [False, s.telopt.nMSDP, True][s.telopt.MSDP + 1]
def __setmsdp(s, v):
  # If a dict was assigned, hand things over to the variable sending.
  if type(v) == dict:
    s.sendMSDP(v)
    return
  old = s.MSDP # Run the appropriate hook if MSDP is enabled or disabled here.
  if type(v) == int and -2 < v < 2: s.telopt.MSDP = v
  else: s.telopt.MSDP = bool(v) and 1 or -1
  if old and not s.MSDP: hooks.run("msdp_disabled", hooks.build_info("sk",(s,)))
  elif not old and s.MSDP: hooks.run("msdp_enabled", hooks.build_info("sk",(s,)))

# Read Only Properties / Methods
def __getnmxp(s):   return s.telopt.nMXP
def __getnmsp(s):   return s.telopt.nMSP
def __getnmsdp(s):  return s.telopt.nMSDP
def __getclient(s): return s.telopt.client
def __getcols(s):   return s.telopt.cols
def __getrows(s):   return s.telopt.rows
def __geteor(s):    return s.telopt.eor
def __getn256(s):   return s.telopt.n256
def __getntitle(s): return s.telopt.ntitle

def __sendMSDP(s, key, val = None):
  '''Send a new MSDP variable to the client if MSDP is enabled. Store the
     variable to be sure not to send repeated information.'''
  if type(key) != dict: key = { key : val }
  
  # Loop through all the variables, building a string
  buf = msdp_start
  count = 0
  for k,v in key.iteritems():
    # If the variable hasn't updated, just continue.
    if s.telopt.vMSDP.has_key(k) and s.telopt.vMSDP[k] == v: continue
    s.telopt.vMSDP[k] = v
    count += 1
    buf += MSDP_VAR + k + __outmsdpv(v)
  
  # If MSDP is enabled and we have at least one updated variable then
  # send the string to the client.
  if s.MSDP and count > 0:
    s.trans_raw(buf + msdp_end)

def __outmsdpv(v):
  '''Output a value for MSDP. Handle lists and tuples too.'''
  if type(v) in [list,tuple]:
    buf = ''
    for k in v: 
      buf += __outmsdpv(k)
    return buf
  else:
    return MSDP_VAL + str(v)

def __bell(s):
  '''Set a flashing marker in the title to let someone know the MUD wants their attention.'''
  # Set the flashing title marker. Until the next recieved command.
  if not s.telopt.bell:
    s.trans_raw('\007\007')
    if s.title:
      s.telopt.bell = True
      s.telopt.bellon = True
      send_title(s)
      event.start_event(s.ch, 1, flash_title)

################################################################################
# Player Commands
################################################################################
def cmd_colors(ch, cmd, arg):
  '''Usage: {Wcolors{n
  
     Displays a table of avaliable colors and the code used to select them.'''
  # Display 256-colors status
  if ch.sock.telopt.c256 == 0:
    if ch.sock.c256:
      if ch.sock.client == 'zmud':
        ch.send("256-colors mode is currently automatically detected as {genabled{n - {gMXP{n.")
      else:
        ch.send("256-colors mode is currently automatically detected as " + mxp_tag(ch.sock,'{genabled{n','B') + ".") #{genabled{n.")
    else:
      ch.send("256-colors mode is currently automatically detected as {rdisabled{n.")
  else:
    if ch.sock.c256: ch.send("256-colors mode is currently set to {genabled{n.")
    else: ch.send("256-colors mode is currenlty set to {rdisabled{n.")
  
  # Output the table of colors
  ch.send("\r\nThe following colors are avaliable:")
    
  # If the screen is wide enough, display the full wide table, else minimize it.
  if ch.sock.cols > 147: im = 35
  else: im = 17
    
  buf = ''
  i = 0
  for k in range(0,16):
    buf += ' {%s%s  ' % (colorletters[k],colorletters[k])
    i += 1
    if i % 6 == 0: buf += ' '
  ch.send(buf)
  i = 0
  buf = ''

  for r in range(0,6):
    for g in range(0, 6):
      for b in range(0, 6):
        buf += '{%d%d%d%d%d%d ' % (r,g,b,r,g,b)
        i += 1
        if i % 6 == 0: buf += ' '
        if i > im:
          ch.send(buf.rstrip())
          buf = ''
          i = 0
  for c in range(0,24):
    buf += '{6%02d6%02d ' % (c,c)
    i += 1
    if i % 6 == 0: buf += ' '
    if i > im:
      ch.send(buf)
      buf = ''
      i = 0
  if i > 0:
    ch.send(buf)

################################################################################
# Initialization
################################################################################
auxiliary.install("telopt", TeloptAux, "socket")

# Fill the extcolors table.
# Disregard that! I put cached values at the start of the file so we don't waste
# time recalculating something easilly predictable.
'''for c in range(0,256):
  extcolors.append(simplecolor(c))
  htmlcolors.append(simplecolor(c,True))'''

# Set our hooks.
hooks.add("receive_connection",      begin_negotiations)
hooks.add("receive_iac",             process_iac_sequence)
hooks.add("process_outbound_text",   process_outbound_text)
hooks.add("process_outbound_prompt", process_outbound_prompt)
hooks.add("command",                 post_command)
hooks.add("mxp_enabled",             mxp_start_hook)
hooks.add("msdp_enabled",            msdp_start_hook)
hooks.add("mxp_disabled",            mxp_stop_hook)
hooks.add("copyover_complete",       copyover_complete)

# Add our commands.
mudsys.add_cmd("colors",   None, cmd_colors,   "player", False)

# Add our properties to the socket.
mudsys.add_sock_method("bell", __bell)
mudsys.add_sock_method("sendMSDP", __sendMSDP)
mudsys.add_sock_method("telopt", property(__gettelopt,__settelopt,doc="The auxiliary data of the advanced_telopt module."))
mudsys.add_sock_method("MXP",    property(__getmxp,__setmxp,__delmxp,
                       "True if MXP output to the client is enabled, else false. Setting a boolean to this property will forcibly enable or disable MXP. Delete this to resume automatic detection of MXP support."))
mudsys.add_sock_method("MSP",    property(__getmsp,__setmsp,__delmsp,
                       "True if MSP output to the client is enabled, else false. Setting a boolean to this property will forcibly enable or disable MSP. Delete this to resume automatic detection of MSP support."))
mudsys.add_sock_method("MSDP",   property(__getmsdp,__setmsdp,__delmsdp,
                       "True if MSDP output to the client is enabled, else false. Setting a boolean to this property will forcibly enable or disable MSDP. Delete this to resume automatic detection of MSDP support."))
mudsys.add_sock_method("title",property(__gettitle,__settitle,__deltitle,
                       "True if title string output to the client is enabled, else false. Setting a string to this property will change the window title message. Setting a boolean to this property will forcibly enable or disable title output. Delete this to determine title support based on the report client."))
mudsys.add_sock_method("c256",   property(__getc256,__setc256,__delc256,
                       "Gets or sets the state of 256-colors mode on the socket. Set to True to enable, False to disable, or delete to use the automatically detect state based on client identification."))

mudsys.add_sock_method("nMXP",   property(__getnmxp,   doc="True if the client has requested MXP, else False."))
mudsys.add_sock_method("nMSP",   property(__getnmsp,   doc="True if the client has requested MSP, else False."))
mudsys.add_sock_method("nMSDP",  property(__getnmsp,   doc="True if the client has requested MSDP, else False."))
mudsys.add_sock_method("nTitle", property(__getntitle, doc="The automatically detected state of window title support, based on client identification."))
#mudsys.add_sock_method("cols",   property(__getcols,   doc="The width of the client's output in columns."))
#mudsys.add_sock_method("rows",   property(__getrows,   doc="The height of the client's output in rows."))
mudsys.add_sock_method("client", property(__getclient, doc="The client software, as given by TELOPT_TTYPE."))
mudsys.add_sock_method("endofrecord",property(__geteor,    doc="True if the client has requested END-OF-RECORD codes, else False. See RFC 885 for more on END-OF-RECORD."))
mudsys.add_sock_method("n256",   property(__getn256,   doc="The automatically detected state of 256-colors mode, based on client identification."))

# Build our list of MXP tags
# These are just some examples, mind you.
'''for k,v in {'RNum':'RoomNum', 'RName':'RoomName', 'RDesc':'RoomDesc', 'RExits':'RoomExit', 'Prompt':'Prompt'}.iteritems():
  register_mxp_flag(k,v)
register_mxp_tag('Ex',    """'<SEND "&text;|look &text;" hint="Right-click to see menu.|Go &text;.|Look &text;." EXPIRE="Exits">'""", True)
register_mxp_tag('REdit', """'<SEND "goto &text;|redit &text;" hint="Right-click to see menu.|Go to &text;|Edit &text;">'""", True)
register_mxp_tag('Help',  """'<SEND "help &text;" hint="Get help for &text;">'""", True)
register_mxp_tag('HEdit', """'<SEND "help &text;|hedit &text;" hint="Right-click to see menu.|Get help for &text;|Edit help for &text;">'""", True)'''

def __unload__():
  ''' Detatch our hooks from the game. '''
  hooks.remove("receive_connection",      begin_negotiations)
  hooks.remove("receive_iac",             process_iac_sequence)
  hooks.remove("process_outbound_text",   process_outbound_text)
  hooks.remove("process_outbound_prompt", process_outbound_prompt)
  hooks.remove("command",                 post_command)
  hooks.remove("mxp_enabled",             mxp_start_hook)
  hooks.remove("mxp_disabled",            mxp_stop_hook)
  hooks.remove("msdp_enabled",            msdp_start_hook)
  hooks.remove("copyover_complete",       copyover_complete)