""" 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: ['>','<','&'][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: ['>','<'][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)