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

View file

@ -0,0 +1,17 @@
"""
Custom at_initial_setup method. This allows you to hook special
modifications to the initial server startup process. Note that this
will only be run once - when the server starts up for the very first
time! It is called last in the startup process and can thus be used to
overload things that happened before it.
The module must contain a global function at_initial_setup(). This
will be called without arguments. Note that tracebacks in this module
will be QUIETLY ignored, so make sure to check it well to make sure it
does what you expect it to.
This module is selected by settings.AT_INITIAL_SETUP_HOOK_MODULE.
"""
def at_initial_setup():
pass

View file

@ -16,9 +16,9 @@
# reboot or reload the server to make them available. # reboot or reload the server to make them available.
# #
from src.utils import utils
from src.commands.connection_screen import DEFAULT_SCREEN from src.commands.connection_screen import DEFAULT_SCREEN
#from src.utils import utils
# #
# CUSTOM_SCREEN = \ # CUSTOM_SCREEN = \
# """{b=============================================================={n # """{b=============================================================={n
@ -32,10 +32,8 @@ from src.commands.connection_screen import DEFAULT_SCREEN
# Enter {whelp{n for more info. {wlook{n will re-load this screen. # Enter {whelp{n for more info. {wlook{n will re-load this screen.
#{b=============================================================={n""" % utils.get_evennia_version() #{b=============================================================={n""" % utils.get_evennia_version()
# # A suggested alternative screen for the Menu login system # # A suggested alternative screen for the Menu login system
# from src.utils import utils
# MENU_SCREEN = \ # MENU_SCREEN = \
# """{b=============================================================={n # """{b=============================================================={n
# Welcome to {gEvennnia{n, version %s! # Welcome to {gEvennnia{n, version %s!

View file

@ -0,0 +1,27 @@
"""
This is an example module for holding custom lock funcs, used in
in-game locks. The modules available to use as lockfuncs are defined
in the tuple settings.LOCK_FUNC_MODULES.
All functions defined globally in this module are assumed to be
available for use in lockstrings to determine access. See
http://code.google.com/p/evennia/wiki/Locks
A lock function is always called with two arguments, accessing_obj and
accessed_obj, followed by any number of arguments. All possible
arguments should be handled (excess ones calling magic (*args,
**kwargs) to avoid errors). The lock function should handle all
eventual tracebacks by logging the error and returning False.
See many more examples of lock functions in src.locks.lockfuncs.
"""
def myfalse(accessing_obj, accessed_obj, *args, **kwargs):
"""
called in lockstring with myfalse().
A simple logger that always returns false. Prints to stdout
for simplicity, should use utils.logger for real operation.
"""
print "%s tried to access %s. Access denied." % (accessing_obj, accessed_obj)
return False

121
game/gamesrc/conf/mssp.py Normal file
View file

@ -0,0 +1,121 @@
#
# MSSP (Mud Server Status Protocol) meta information
#
# MUD website listings (that you have registered with) can use this
# information to keep up-to-date with your game stats as you change
# them. Also number of currently active players and uptime will
# automatically be reported. You don't have to fill in everything
# (and most are not used by all crawlers); leave the default
# if so needed. You need to @reload the game before updated
# information is made available to crawlers (reloading does not
# affect uptime).
#
MSSPTable = {
# Required fieldss
"NAME": "Evennia",
# Generic
"CRAWL DELAY": "-1", # limit how often crawler updates the listing. -1 for no limit
"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": "", # 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 or 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"}

View file

@ -0,0 +1,16 @@
#
# Example module holding functions for out-of-band protocols to
# import and map to given commands from the client. This module
# is selected by settings.OOB_FUNC_MODULE.
#
# All functions defined global in this module will be available
# for the oob system to call. They will be called with a session/character
# as first argument (depending on if the session is logged in or not),
# following by any number of extra arguments. The return value will
# be packed and returned to the oob protocol and can be on any form.
#
def testoob(character, *args, **kwargs):
"Simple test function"
print "Called testoob: %s" % val
return "testoob did stuff to the input string '%s'!" % val

View file

@ -237,13 +237,13 @@ class CmdUnconnectedLook(MuxCommand):
string = ansi.parse_ansi(screen) string = ansi.parse_ansi(screen)
self.caller.msg(string) self.caller.msg(string)
except Exception, e: except Exception, e:
self.caller.msg(e) self.caller.msg("Error in CONNECTION_SCREEN MODULE: " + str(e))
self.caller.msg("Connect screen not found. Enter 'help' for aid.") self.caller.msg("Connect screen not found. Enter 'help' for aid.")
class CmdUnconnectedHelp(MuxCommand): class CmdUnconnectedHelp(MuxCommand):
""" """
This is an unconnected version of the help command, This is an unconnected version of the help command,
for simplicity. It shows a pane or info. for simplicity. It shows a pane of info.
""" """
key = "help" key = "help"
aliases = ["h", "?"] aliases = ["h", "?"]

View file

@ -210,14 +210,14 @@ class AMPProtocol(amp.AMP):
""" """
if hasattr(self.factory, "portal"): if hasattr(self.factory, "portal"):
sessdata = self.factory.portal.sessions.get_all_sync_data() sessdata = self.factory.portal.sessions.get_all_sync_data()
print sessdata #print sessdata
self.call_remote_ServerAdmin(0, self.call_remote_ServerAdmin(0,
"PSYNC", "PSYNC",
data=sessdata) data=sessdata)
if get_restart_mode(SERVER_RESTART): if get_restart_mode(SERVER_RESTART):
msg = _(" ... Server restarted.") msg = _(" ... Server restarted.")
self.factory.portal.sessions.announce_all(msg) self.factory.portal.sessions.announce_all(msg)
self.factory.portal.sessions.at_server_connection()
# Error handling # 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 compress data when sending to supporting clients, reducing bandwidth
by 70-90%.. The compression is done using Python's builtin zlib by 70-90%.. The compression is done using Python's builtin zlib
library. If the client doesn't support MCCP, server sends uncompressed 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 effect of MCCP unless you have extremely heavy traffic or sits on a
terribly slow connection. terribly slow connection.
This protocol is implemented by the telnet protocol importing
mccp_compress and calling it from its write methods.
""" """
import zlib import zlib
@ -21,8 +23,7 @@ FLUSH = zlib.Z_SYNC_FLUSH
def mccp_compress(protocol, data): def mccp_compress(protocol, data):
"Handles zlib compression, if applicable" "Handles zlib compression, if applicable"
if hasattr(protocol, 'zlib'): if hasattr(protocol, 'zlib'):
data = protocol.zlib.compress(data) return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
data += protocol.zlib.flush(FLUSH)
return data return data
class Mccp(object): class Mccp(object):
@ -48,7 +49,6 @@ class Mccp(object):
""" """
If client doesn't support mccp, don't do anything. If client doesn't support mccp, don't do anything.
""" """
print "deactivating mccp ..."
if hasattr(self.protocol, 'zlib'): if hasattr(self.protocol, 'zlib'):
del self.protocol.zlib del self.protocol.zlib
self.protocol.protocol_flags['MCCP'] = False self.protocol.protocol_flags['MCCP'] = False
@ -58,7 +58,6 @@ class Mccp(object):
The client supports MCCP. Set things up by The client supports MCCP. Set things up by
creating a zlib compression stream. creating a zlib compression stream.
""" """
print "activating mccp ..."
self.protocol.protocol_flags['MCCP'] = True self.protocol.protocol_flags['MCCP'] = True
self.protocol.requestNegotiation(MCCP, '') self.protocol.requestNegotiation(MCCP, '')
self.protocol.zlib = zlib.compressobj(9) 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. # names of attributes that should be affected by syncing.
_attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname', _attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
'logged_in', 'cid', 'encoding', '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): def init_session(self, protocol_key, address, sessionhandler):
""" """
@ -72,6 +73,7 @@ class Session(object):
self.cmd_total = 0 self.cmd_total = 0
self.protocol_flags = {} self.protocol_flags = {}
self.server_data = {}
# a back-reference to the relevant sessionhandler this # a back-reference to the relevant sessionhandler this
# session is stored in. # session is stored in.

View file

@ -89,6 +89,7 @@ class ServerSessionHandler(SessionHandler):
""" """
self.sessions = {} self.sessions = {}
self.server = None self.server = None
self.server_data = {"servername":settings.SERVERNAME}
def portal_connect(self, sessid, session): def portal_connect(self, sessid, session):
""" """
@ -333,6 +334,16 @@ class PortalSessionHandler(SessionHandler):
self.portal = None self.portal = None
self.sessions = {} self.sessions = {}
self.latest_sessid = 0 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): def connect(self, session):
""" """
@ -373,6 +384,14 @@ class PortalSessionHandler(SessionHandler):
session.disconnect(reason) session.disconnect(reason)
del session 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): def session_from_suid(self, suid):
""" """
Given a session id, retrieve the session (this is primarily 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 twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE, DO, DONT
from src.server.session import Session 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 from src.utils import utils, ansi
class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
@ -27,11 +28,14 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
client_address = self.transport.client client_address = self.transport.client
self.init_session("telnet", client_address, self.factory.sessionhandler) self.init_session("telnet", client_address, self.factory.sessionhandler)
# setup ttype (client info) # negotiate mccp (data compression)
#self.ttype = ttype.Ttype(self) self.mccp = Mccp(self)
# setup mccp (data compression) # negotiate ttype (client info)
# self.mccp = mccp.Mccp(self) #TODO: mccp doesn't work quite right yet. self.ttype = ttype.Ttype(self)
# negotiate mssp (crawler communication)
self.mssp = mssp.Mssp(self)
# add us to sessionhandler # add us to sessionhandler
self.sessionhandler.connect(self) self.sessionhandler.connect(self)
@ -42,18 +46,17 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
""" """
return (option == LINEMODE or return (option == LINEMODE or
option == ttype.TTYPE or option == ttype.TTYPE or
option == mccp.MCCP) option == MCCP or
option == mssp.MSSP)
def enableLocal(self, option): def enableLocal(self, option):
""" """
Allow certain options on this protocol Allow certain options on this protocol
""" """
if option == mccp.MCCP: return option == MCCP
#self.mccp.do_mccp(option)
return True
def disableLocal(self, option): def disableLocal(self, option):
if option == mccp.MCCP: if option == MCCP:
self.mccp.no_mccp(option) self.mccp.no_mccp(option)
return True return True
else: else:
@ -84,19 +87,26 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# print str(e) + ":", str(data) # print str(e) + ":", str(data)
if data and data[0] == IAC: if data and data[0] == IAC:
super(TelnetProtocol, self).dataReceived(data) try:
else: super(TelnetProtocol, self).dataReceived(data)
StatefulTelnetProtocol.dataReceived(self, 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" "hook overloading the one used in plain telnet"
#print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in byt)) #print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in data))
super(TelnetProtocol, self)._write(mccp.mccp_compress(self, byt)) data = data.replace('\n', '\r\n')
super(TelnetProtocol, self)._write(mccp_compress(self, data))
def sendLine(self, line): 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) #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): def lineReceived(self, string):
""" """
@ -105,6 +115,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
""" """
self.sessionhandler.data_in(self, string) self.sessionhandler.data_in(self, string)
# Session hooks # Session hooks
def disconnect(self, reason=None): def disconnect(self, reason=None):

View file

@ -157,12 +157,14 @@ SEARCH_AT_MULTIMATCH_INPUT = "src.commands.cmdparser.at_multimatch_input"
# The module holding text strings for the connection screen. # The module holding text strings for the connection screen.
# This module should contain one or more variables # This module should contain one or more variables
# with strings defining the look of the screen. # with strings defining the look of the screen.
CONNECTION_SCREEN_MODULE = "game.gamesrc.world.connection_screens" CONNECTION_SCREEN_MODULE = "game.gamesrc.conf.connection_screens"
# An option al module that, if existing, must hold a function # An option al module that, if existing, must hold a function
# named at_initial_setup(). This hook method can be used to customize # named at_initial_setup(). This hook method can be used to customize
# the server's initial setup sequence (the very first startup of the system). # the server's initial setup sequence (the very first startup of the system).
# The check will fail quietly if module doesn't exist or fails to load. # The check will fail quietly if module doesn't exist or fails to load.
AT_INITIAL_SETUP_HOOK_MODULE = "game.gamesrc.world.at_initial_setup" AT_INITIAL_SETUP_HOOK_MODULE = "game.gamesrc.conf.at_initial_setup"
# Module holding server-side functions for out-of-band protocols to call.
OOB_FUNC_MODULE = "game.gamesrc.conf.oobfuncs"
################################################### ###################################################
# Default command sets # Default command sets
@ -251,9 +253,7 @@ PERMISSION_HIERARCHY = ("Players","PlayerHelpers","Builders", "Wizards", "Immort
PERMISSION_PLAYER_DEFAULT = "Players" PERMISSION_PLAYER_DEFAULT = "Players"
# Tuple of modules implementing lock functions. All callable functions # Tuple of modules implementing lock functions. All callable functions
# inside these modules will be available as lock functions. # inside these modules will be available as lock functions.
LOCK_FUNC_MODULES = ("src.locks.lockfuncs",) LOCK_FUNC_MODULES = ("src.locks.lockfuncs","game.gamesrc.conf.lockfuncs")
# Module holding server-side functions for out-of-band protocols to call.
OOB_FUNC_MODULE = ""
################################################### ###################################################

View file

@ -604,25 +604,45 @@ def mod_import(mod_path, propname=None):
return mod_prop return mod_prop
return mod return mod
def string_from_module(modpath, variable=None): def variable_from_module(modpath, variable, default=None):
""" """
This obtains a string from a given module python path. Retrieve a given variable from a module. The variable must be
The variable must be global within that module - that is, defined in defined globally in the module. This can be used to implement
the outermost scope of the module. The value of the arbitrary plugin imports in the server.
variable will be returned. If not found (or if it's not a string),
None is returned.
This is useful primarily for storing various game strings If module cannot be imported or variable not found, default
in a module and extract them by name or randomly. is returned.
"""
try:
mod = __import__(modpath, fromlist=["None"])
return mod.__dict__.get(variable, default)
except ImportError:
return default
def string_from_module(modpath, variable=None, default=None):
"""
This is a variation used primarily to get login screens randomly
from a module.
This obtains a string from a given module python path. Using a
specific variable name will also retrieve non-strings.
The variable must be global within that module - that is, defined
in the outermost scope of the module. The value of the variable
will be returned. If not found, default is returned. If no variable is
given, a random string variable is returned.
This is useful primarily for storing various game strings in a
module and extract them by name or randomly.
""" """
mod = __import__(modpath, fromlist=[None]) mod = __import__(modpath, fromlist=[None])
if variable: if variable:
return mod.__dict__.get(variable, None) return mod.__dict__.get(variable, default)
else: else:
mvars = [val for key, val in mod.__dict__.items() mvars = [val for key, val in mod.__dict__.items()
if not key.startswith('_') and isinstance(val, basestring)] if not key.startswith('_') and isinstance(val, basestring)]
if not mvars: if not mvars:
return None return default
return mvars[random.randint(0, len(mvars)-1)] return mvars[random.randint(0, len(mvars)-1)]
def init_new_player(player): def init_new_player(player):