Implemented working MCCP (data compression) and MSSP (mud-listing crawler support). Moved all user-level customization modules from gamesrc/world to gamesrc/conf to reduce clutter.

This commit is contained in:
Griatch 2011-11-20 11:52:01 +01:00
parent a4f8019c4a
commit fb78758356
15 changed files with 465 additions and 48 deletions

View file

@ -210,14 +210,14 @@ class AMPProtocol(amp.AMP):
"""
if hasattr(self.factory, "portal"):
sessdata = self.factory.portal.sessions.get_all_sync_data()
print sessdata
#print sessdata
self.call_remote_ServerAdmin(0,
"PSYNC",
data=sessdata)
if get_restart_mode(SERVER_RESTART):
msg = _(" ... Server restarted.")
self.factory.portal.sessions.announce_all(msg)
self.factory.portal.sessions.at_server_connection()
# Error handling

View file

@ -7,10 +7,12 @@ http://tintin.sourceforge.net/mccp/. MCCP allows for the server to
compress data when sending to supporting clients, reducing bandwidth
by 70-90%.. The compression is done using Python's builtin zlib
library. If the client doesn't support MCCP, server sends uncompressed
instead. Note: On modern hardware you are not likely to notice the
as normal. Note: On modern hardware you are not likely to notice the
effect of MCCP unless you have extremely heavy traffic or sits on a
terribly slow connection.
This protocol is implemented by the telnet protocol importing
mccp_compress and calling it from its write methods.
"""
import zlib
@ -21,8 +23,7 @@ FLUSH = zlib.Z_SYNC_FLUSH
def mccp_compress(protocol, data):
"Handles zlib compression, if applicable"
if hasattr(protocol, 'zlib'):
data = protocol.zlib.compress(data)
data += protocol.zlib.flush(FLUSH)
return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
return data
class Mccp(object):
@ -47,8 +48,7 @@ class Mccp(object):
def no_mccp(self, option):
"""
If client doesn't support mccp, don't do anything.
"""
print "deactivating mccp ..."
"""
if hasattr(self.protocol, 'zlib'):
del self.protocol.zlib
self.protocol.protocol_flags['MCCP'] = False
@ -58,7 +58,6 @@ class Mccp(object):
The client supports MCCP. Set things up by
creating a zlib compression stream.
"""
print "activating mccp ..."
self.protocol.protocol_flags['MCCP'] = True
self.protocol.requestNegotiation(MCCP, '')
self.protocol.zlib = zlib.compressobj(9)

187
src/server/mssp.py Normal file
View file

@ -0,0 +1,187 @@
"""
MSSP - Mud Server Status Protocol
This implements the MSSP telnet protocol as per
http://tintin.sourceforge.net/mssp/. MSSP allows web portals and
listings to have their crawlers find the mud and automatically
extract relevant information about it, such as genre, how many
active players and so on.
Most of these settings are de
"""
from src.utils import utils
MSSP = chr(70)
MSSP_VAR = chr(1)
MSSP_VAL = chr(2)
# try to get the customized mssp info, if it exists.
MSSPTable_CUSTOM = utils.variable_from_module("game.gamesrc.conf.mssp", "MSSPTable", default={})
class Mssp(object):
"""
Implements the MSSP protocol. Add this to a
variable on the telnet protocol to set it up.
"""
def __init__(self, protocol):
"""
initialize MSSP by storing protocol on ourselves
and calling the client to see if it supports
MSSP.
"""
self.protocol = protocol
self.protocol.will(MSSP).addCallbacks(self.do_mssp, self.no_mssp)
def get_player_count(self):
"Get number of logged-in players"
return str(self.protocol.sessionhandler.count_loggedin())
def get_uptime(self):
"Get how long the portal has been online (reloads are not counted)"
return str(self.protocol.sessionhandler.uptime)
def no_mssp(self, option):
"""
This is the normal operation.
"""
print "no mssp"
pass
def do_mssp(self, option):
"""
Negotiate all the information.
"""
self.mssp_table = {
# Required fields
"NAME": "Evennia",
"PLAYERS": self.get_player_count,
"UPTIME" : self.get_uptime,
# Generic
"CRAWL DELAY": "-1",
"HOSTNAME": "", # current or new hostname
"PORT": ["4000"], # most important port should be last in list
"CODEBASE": "Evennia",
"CONTACT": "", # email for contacting the mud
"CREATED": "", # year MUD was created
"ICON": "", # url to icon 32x32 or larger; <32kb.
"IP": "", # current or new IP address
"LANGUAGE": "", # name of language used, e.g. English
"LOCATION": "", # full English name of server country
"MINIMUM AGE": "0", # set to 0 if not applicable
"WEBSITE": "www.evennia.com",
# Categorisation
"FAMILY": "Custom", # evennia goes under 'Custom'
"GENRE": "None", # Adult, Fantasy, Historical, Horror, Modern, None, or Science Fiction
"GAMEPLAY": "None", # Adventure, Educational, Hack and Slash, None,
# Player versus Player, Player versus Environment,
# Roleplaying, Simulation, Social or Strategy
"STATUS": "Open Beta", # Alpha, Closed Beta, Open Beta, Live
"GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew
"INTERMUD": "IMC2", # evennia supports IMC2.
"SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein,
# Cyberpunk, Dragonlance, etc. Or None if not available.
# World
"AREAS": "0",
"HELPFILES": "0",
"MOBILES": "0",
"OBJECTS": "0",
"ROOMS": "0", # use 0 if room-less
"CLASSES": "0", # use 0 if class-less
"LEVELS": "0", # use 0 if level-less
"RACES": "0", # use 0 if race-less
"SKILLS": "0", # use 0 if skill-less
# Protocols set to 1 or 0)
"ANSI": "1",
"GMCP": "0",
"MCCP": "0",
"MCP": "0",
"MSDP": "0",
"MSP": "0",
"MXP": "0",
"PUEBLO": "0",
"UTF-8": "1",
"VT100": "0",
"XTERM 256 COLORS": "0",
# Commercial set to 1 or 0)
"PAY TO PLAY": "0",
"PAY FOR PERKS": "0",
# Hiring set to 1 or 0)
"HIRING BUILDERS": "0",
"HIRING CODERS": "0",
# Extended variables
# World
"DBSIZE": "0",
"EXITS": "0",
"EXTRA DESCRIPTIONS": "0",
"MUDPROGS": "0",
"MUDTRIGS": "0",
"RESETS": "0",
# Game (set to 1, 0 or one of the given alternatives)
"ADULT MATERIAL": "0",
"MULTICLASSING": "0",
"NEWBIE FRIENDLY": "0",
"PLAYER CITIES": "0",
"PLAYER CLANS": "0",
"PLAYER CRAFTING": "0",
"PLAYER GUILDS": "0",
"EQUIPMENT SYSTEM": "None", # "None", "Level", "Skill", "Both"
"MULTIPLAYING": "None", # "None", "Restricted", "Full"
"PLAYERKILLING": "None", # "None", "Restricted", "Full"
"QUEST SYSTEM": "None", # "None", "Immortal Run", "Automated", "Integrated"
"ROLEPLAYING": "None", # "None", "Accepted", "Encouraged", "Enforced"
"TRAINING SYSTEM": "None", # "None", "Level", "Skill", "Both"
"WORLD ORIGINALITY": "None", # "All Stock", "Mostly Stock", "Mostly Original", "All Original"
# Protocols (only change if you added/removed something manually)
"ATCP": "0",
"MSDP": "0",
"MCCP": "1",
"SSL": "1",
"UTF-8": "1",
"ZMP": "0",
"XTERM 256 COLORS": "0"}
# update the static table with the custom one
self.mssp_table.update(MSSPTable_CUSTOM)
varlist = ''
for variable, value in self.mssp_table.items():
if callable(value):
value = value()
if utils.is_iter(value):
for partval in value:
varlist += MSSP_VAR + str(variable) + MSSP_VAL + str(partval)
else:
varlist += MSSP_VAR + str(variable) + MSSP_VAL + str(value)
# send to crawler by subnegotiation
self.protocol.requestNegotiation(MSSP, varlist)

View file

@ -35,7 +35,8 @@ class Session(object):
# names of attributes that should be affected by syncing.
_attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
'logged_in', 'cid', 'encoding',
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total', 'protocol_flags']
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
'protocol_flags', 'server_data']
def init_session(self, protocol_key, address, sessionhandler):
"""
@ -72,6 +73,7 @@ class Session(object):
self.cmd_total = 0
self.protocol_flags = {}
self.server_data = {}
# a back-reference to the relevant sessionhandler this
# session is stored in.

View file

@ -89,6 +89,7 @@ class ServerSessionHandler(SessionHandler):
"""
self.sessions = {}
self.server = None
self.server_data = {"servername":settings.SERVERNAME}
def portal_connect(self, sessid, session):
"""
@ -333,6 +334,16 @@ class PortalSessionHandler(SessionHandler):
self.portal = None
self.sessions = {}
self.latest_sessid = 0
self.uptime = time.time()
self.connection_time = 0
def at_server_connection(self):
"""
Called when the Portal establishes connection with the
Server. At this point, the AMP connection is already
established.
"""
self.connection_time = time.time()
def connect(self, session):
"""
@ -373,6 +384,14 @@ class PortalSessionHandler(SessionHandler):
session.disconnect(reason)
del session
def count_loggedin(self, include_unloggedin=False):
"""
Count loggedin connections, alternatively count all connections.
"""
return len(self.get_sessions(include_unloggedin=include_unloggedin))
def session_from_suid(self, suid):
"""
Given a session id, retrieve the session (this is primarily

View file

@ -9,7 +9,8 @@ sessions etc.
from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE, DO, DONT
from src.server.session import Session
from src.server import ttype, mccp
from src.server import ttype, mssp
from src.server.mccp import Mccp, mccp_compress, MCCP
from src.utils import utils, ansi
class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
@ -27,11 +28,14 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
client_address = self.transport.client
self.init_session("telnet", client_address, self.factory.sessionhandler)
# setup ttype (client info)
#self.ttype = ttype.Ttype(self)
# negotiate mccp (data compression)
self.mccp = Mccp(self)
# negotiate ttype (client info)
self.ttype = ttype.Ttype(self)
# setup mccp (data compression)
# self.mccp = mccp.Mccp(self) #TODO: mccp doesn't work quite right yet.
# negotiate mssp (crawler communication)
self.mssp = mssp.Mssp(self)
# add us to sessionhandler
self.sessionhandler.connect(self)
@ -42,18 +46,17 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
"""
return (option == LINEMODE or
option == ttype.TTYPE or
option == mccp.MCCP)
option == MCCP or
option == mssp.MSSP)
def enableLocal(self, option):
"""
Allow certain options on this protocol
"""
if option == mccp.MCCP:
#self.mccp.do_mccp(option)
return True
return option == MCCP
def disableLocal(self, option):
if option == mccp.MCCP:
if option == MCCP:
self.mccp.no_mccp(option)
return True
else:
@ -84,19 +87,26 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# print str(e) + ":", str(data)
if data and data[0] == IAC:
super(TelnetProtocol, self).dataReceived(data)
else:
StatefulTelnetProtocol.dataReceived(self, data)
try:
super(TelnetProtocol, self).dataReceived(data)
return
except Exception:
pass
StatefulTelnetProtocol.dataReceived(self, data)
def _write(self, byt):
def _write(self, data):
"hook overloading the one used in plain telnet"
#print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in byt))
super(TelnetProtocol, self)._write(mccp.mccp_compress(self, byt))
#print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in data))
data = data.replace('\n', '\r\n')
super(TelnetProtocol, self)._write(mccp_compress(self, data))
def sendLine(self, line):
"hook overloading the one used linereceiver"
"hook overloading the one used by linereceiver"
#print "sendLine (%s):\n%s" % (self.state, line)
super(TelnetProtocol, self).sendLine(mccp.mccp_compress(self, line))
#escape IAC in line mode, and correctly add \r\n
line += self.delimiter
line = line.replace(IAC, IAC + IAC).replace('\n', '\r\n')
return self.transport.write(mccp_compress(self, line))
def lineReceived(self, string):
"""
@ -105,6 +115,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
"""
self.sessionhandler.data_in(self, string)
# Session hooks
def disconnect(self, reason=None):