mirror of
https://github.com/evennia/evennia.git
synced 2026-03-21 15:26:30 +01:00
335 lines
12 KiB
Python
335 lines
12 KiB
Python
"""
|
|
This defines a the Server's generic session object. This object represents
|
|
a connection to the outside world but don't know any details about how the
|
|
connection actually happens (so it's the same for telnet, web, ssh etc).
|
|
|
|
It is stored on the Server side (as opposed to protocol-specific sessions which
|
|
are stored on the Portal side)
|
|
"""
|
|
|
|
import time
|
|
from datetime import datetime
|
|
from django.conf import settings
|
|
from src.scripts.models import ScriptDB
|
|
from src.comms.models import Channel
|
|
from src.utils import logger, utils
|
|
from src.utils.utils import make_iter
|
|
from src.commands import cmdhandler, cmdsethandler
|
|
from src.server.session import Session
|
|
|
|
IDLE_COMMAND = settings.IDLE_COMMAND
|
|
_GA = object.__getattribute__
|
|
_ObjectDB = None
|
|
|
|
# load optional out-of-band function module
|
|
OOB_PLUGIN_MODULE = settings.OOB_PLUGIN_MODULE
|
|
if OOB_PLUGIN_MODULE:
|
|
OOB_PLUGIN_MODULE = utils.mod_import(settings.OOB_PLUGIN_MODULE)
|
|
|
|
# i18n
|
|
from django.utils.translation import ugettext as _
|
|
|
|
|
|
#------------------------------------------------------------
|
|
# Server Session
|
|
#------------------------------------------------------------
|
|
|
|
class ServerSession(Session):
|
|
"""
|
|
This class represents a player's session and is a template for
|
|
individual protocols to communicate with Evennia.
|
|
|
|
Each player gets a session assigned to them whenever they connect
|
|
to the game server. All communication between game and player goes
|
|
through their session.
|
|
|
|
"""
|
|
def __init__(self):
|
|
"Initiate to avoid AttributeErrors down the line"
|
|
self.puppet = None
|
|
self.player = None
|
|
self.cmdset_storage_string = ""
|
|
self.cmdset = cmdsethandler.CmdSetHandler(self)
|
|
|
|
def __cmdset_storage_get(self):
|
|
return [path.strip() for path in self.cmdset_storage_string.split(',')]
|
|
def __cmdset_storage_set(self, value):
|
|
self.cmdset_storage_string = ",".join(str(val).strip() for val in make_iter(value))
|
|
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set)
|
|
|
|
def at_sync(self):
|
|
"""
|
|
This is called whenever a session has been resynced with the portal.
|
|
At this point all relevant attributes have already been set and self.player
|
|
been assigned (if applicable).
|
|
|
|
Since this is often called after a server restart we need to set up
|
|
the session as it was.
|
|
"""
|
|
global _ObjectDB
|
|
if not _ObjectDB:
|
|
from src.objects.models import ObjectDB as _ObjectDB
|
|
|
|
if not self.logged_in:
|
|
# assign the unloggedin-command set.
|
|
self.cmdset_storage = settings.CMDSET_UNLOGGEDIN
|
|
|
|
self.cmdset.update(init_mode=True)
|
|
|
|
if self.puid:
|
|
# reconnect puppet (puid is only set if we are coming back from a server reload)
|
|
obj = _ObjectDB.objects.get(id=self.puid)
|
|
self.player.puppet_object(self.sessid, obj, normal_mode=False)
|
|
|
|
def at_login(self, player):
|
|
"""
|
|
Hook called by sessionhandler when the session becomes authenticated.
|
|
|
|
player - the player associated with the session
|
|
"""
|
|
self.player = player
|
|
self.uid = self.player.id
|
|
self.uname = self.player.username
|
|
self.logged_in = True
|
|
self.conn_time = time.time()
|
|
self.puid = None
|
|
self.puppet = None
|
|
self.cmdset_storage = settings.CMDSET_SESSION
|
|
|
|
# Update account's last login time.
|
|
self.player.last_login = datetime.now()
|
|
self.player.save()
|
|
|
|
# add the session-level cmdset
|
|
self.cmdset = cmdsethandler.CmdSetHandler(self)
|
|
self.cmdset.update(init_mode=True)
|
|
|
|
def at_disconnect(self):
|
|
"""
|
|
Hook called by sessionhandler when disconnecting this session.
|
|
"""
|
|
if self.logged_in:
|
|
sessid = self.sessid
|
|
player = self.player
|
|
_GA(player.dbobj, "unpuppet_object")(sessid)
|
|
uaccount = player.dbobj
|
|
uaccount.last_login = datetime.now()
|
|
uaccount.save()
|
|
# calling player hook
|
|
_GA(player.typeclass, "at_disconnect")()
|
|
self.logged_in = False
|
|
if not self.sessionhandler.sessions_from_player(player):
|
|
# no more sessions connected to this player
|
|
player.is_connected = False
|
|
|
|
def get_player(self):
|
|
"""
|
|
Get the player associated with this session
|
|
"""
|
|
return self.logged_in and self.player
|
|
|
|
def get_puppet(self):
|
|
"""
|
|
Returns the in-game character associated with this session.
|
|
This returns the typeclass of the object.
|
|
"""
|
|
return self.logged_in and self.puppet
|
|
get_character = get_puppet
|
|
|
|
def log(self, message, channel=True):
|
|
"""
|
|
Emits session info to the appropriate outputs and info channels.
|
|
"""
|
|
if channel:
|
|
try:
|
|
cchan = settings.CHANNEL_CONNECTINFO
|
|
cchan = Channel.objects.get_channel(cchan[0])
|
|
cchan.msg("[%s]: %s" % (cchan.key, message))
|
|
except Exception:
|
|
pass
|
|
logger.log_infomsg(message)
|
|
|
|
def update_session_counters(self, idle=False):
|
|
"""
|
|
Hit this when the user enters a command in order to update idle timers
|
|
and command counters.
|
|
"""
|
|
# Store the timestamp of the user's last command.
|
|
self.cmd_last = time.time()
|
|
if not idle:
|
|
# Increment the user's command counter.
|
|
self.cmd_total += 1
|
|
# Player-visible idle time, not used in idle timeout calcs.
|
|
self.cmd_last_visible = time.time()
|
|
|
|
def data_in(self, command_string):
|
|
"""
|
|
Send Player->Evennia. This will in effect
|
|
execute a command string on the server.
|
|
Eventual extra data moves through oob_data_in
|
|
"""
|
|
# handle the 'idle' command
|
|
if str(command_string).strip() == IDLE_COMMAND:
|
|
self.update_session_counters(idle=True)
|
|
return
|
|
cmdhandler.cmdhandler(self, command_string, callertype="session", sessid=self.sessid)
|
|
#if self.logged_in:
|
|
# # the inmsg handler will relay to the right place
|
|
# self.player.inmsg(command_string, self)
|
|
#else:
|
|
# # we are not logged in. Execute cmd with the the session directly
|
|
# # (it uses the settings.UNLOGGEDIN cmdset)
|
|
# cmdhandler.cmdhandler(self, command_string, sessid=self.sessid)
|
|
self.update_session_counters()
|
|
execute_cmd = data_in # alias
|
|
|
|
def data_out(self, text=None, **kwargs):
|
|
"""
|
|
Send Evennia -> Player
|
|
"""
|
|
self.sessionhandler.data_out(self, text=text, **kwargs)
|
|
|
|
def oob_data_in(self, data):
|
|
"""
|
|
This receives out-of-band data from the Portal.
|
|
|
|
OBS - preliminary. OOB not yet functional in Evennia. Don't use.
|
|
|
|
This method parses the data input (a dict) and uses
|
|
it to launch correct methods from those plugged into
|
|
the system.
|
|
|
|
data = {oobkey: (funcname, (args), {kwargs}),
|
|
oobkey: (funcname, (args), {kwargs}), ...}
|
|
|
|
example:
|
|
data = {"get_hp": ("oob_get_hp, [], {}),
|
|
"update_counter", ("counter", ["counter1"], {"now":True}) }
|
|
|
|
All function names must be defined in settings.OOB_PLUGIN_MODULE. Each
|
|
function will be called with the oobkey and a back-reference to this session
|
|
as their first two arguments.
|
|
"""
|
|
|
|
outdata = {}
|
|
|
|
for oobkey, functuple in data.items():
|
|
# loop through the data, calling available functions.
|
|
func = OOB_PLUGIN_MODULE.__dict__.get(functuple[0])
|
|
if func:
|
|
try:
|
|
outdata[functuple[0]] = func(oobkey, self, *functuple[1], **functuple[2])
|
|
except Exception:
|
|
logger.log_trace()
|
|
else:
|
|
logger.log_errmsg("oob_data_in error: funcname '%s' not found in OOB_PLUGIN_MODULE." % functuple[0])
|
|
if outdata:
|
|
# we have a direct result - send it back right away
|
|
self.oob_data_out(outdata)
|
|
|
|
|
|
def oob_data_out(self, data):
|
|
"""
|
|
This sends data from Server to the Portal across the AMP connection.
|
|
|
|
OBS - preliminary. OOB not yet functional in Evennia. Don't use.
|
|
|
|
data = {oobkey: (funcname, (args), {kwargs}),
|
|
oobkey: (funcname, (args), {kwargs}), ...}
|
|
|
|
"""
|
|
self.sessionhandler.oob_data_out(self, data)
|
|
|
|
|
|
def __eq__(self, other):
|
|
return self.address == other.address
|
|
|
|
|
|
def __str__(self):
|
|
"""
|
|
String representation of the user session class. We use
|
|
this a lot in the server logs.
|
|
"""
|
|
symbol = ""
|
|
if self.logged_in and hasattr(self, "player") and self.player:
|
|
symbol = "(#%s)" % self.player.id
|
|
try:
|
|
if hasattr(self.address, '__iter__'):
|
|
address = ":".join([str(part) for part in self.address])
|
|
else:
|
|
address = self.address
|
|
except Exception:
|
|
address = self.address
|
|
return "%s%s@%s" % (self.uname, symbol, address)
|
|
|
|
def __unicode__(self):
|
|
"""
|
|
Unicode representation
|
|
"""
|
|
return u"%s" % str(self)
|
|
|
|
# easy-access functions
|
|
|
|
#def login(self, player):
|
|
# "alias for at_login"
|
|
# self.session_login(player)
|
|
#def disconnect(self):
|
|
# "alias for session_disconnect"
|
|
# self.session_disconnect()
|
|
def msg(self, text='', **kwargs):
|
|
"alias for at_data_out"
|
|
self.data_out(text=text, **kwargs)
|
|
|
|
|
|
# Dummy API hooks for use during non-loggedin operation
|
|
|
|
def at_cmdset_get(self):
|
|
"dummy hook all objects with cmdsets need to have"
|
|
pass
|
|
|
|
# Mock db/ndb properties for allowing easy storage on the session
|
|
# (note that no databse is involved at all here. session.db.attr =
|
|
# value just saves a normal property in memory, just like ndb).
|
|
|
|
#@property
|
|
def ndb_get(self):
|
|
"""
|
|
A non-persistent store (ndb: NonDataBase). Everything stored
|
|
to this is guaranteed to be cleared when a server is shutdown.
|
|
Syntax is same as for the _get_db_holder() method and
|
|
property, e.g. obj.ndb.attr = value etc.
|
|
"""
|
|
try:
|
|
return self._ndb_holder
|
|
except AttributeError:
|
|
class NdbHolder(object):
|
|
"Holder for storing non-persistent attributes."
|
|
def all(self):
|
|
return [val for val in self.__dict__.keys()
|
|
if not val.startswith['_']]
|
|
def __getattribute__(self, key):
|
|
# return None if no matching attribute was found.
|
|
try:
|
|
return object.__getattribute__(self, key)
|
|
except AttributeError:
|
|
return None
|
|
self._ndb_holder = NdbHolder()
|
|
return self._ndb_holder
|
|
#@ndb.setter
|
|
def ndb_set(self, value):
|
|
"Stop accidentally replacing the db object"
|
|
string = "Cannot assign directly to ndb object! "
|
|
string = "Use ndb.attr=value instead."
|
|
raise Exception(string)
|
|
#@ndb.deleter
|
|
def ndb_del(self):
|
|
"Stop accidental deletion."
|
|
raise Exception("Cannot delete the ndb object!")
|
|
ndb = property(ndb_get, ndb_set, ndb_del)
|
|
db = property(ndb_get, ndb_set, ndb_del)
|
|
|
|
# Mock access method for the session (there is no lock info
|
|
# at this stage, so we just present a uniform API)
|
|
def access(self, *args, **kwargs):
|
|
"Dummy method."
|
|
return True
|