mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 05:16:31 +01:00
412 lines
13 KiB
Python
412 lines
13 KiB
Python
"""
|
|
This defines a generic session class.
|
|
|
|
All protocols should implement this class and its hook methods.
|
|
|
|
|
|
The process of first connect:
|
|
|
|
- The custom connection-handler for the respective
|
|
protocol should be called by the transport connection itself.
|
|
- The connect-handler handles whatever internal settings are needed
|
|
- The connection-handler calls session_connect()
|
|
- session_connect() setups sessions then calls session.at_connect()
|
|
|
|
Disconnecting is a bit more complex in order to avoid circular calls
|
|
depending on if the disconnect happens automatically or manually from
|
|
a command.
|
|
|
|
The process at automatic disconnect:
|
|
- The custom disconnect-handler for the respective protocol
|
|
should be called by the transport connection itself. This handler
|
|
should be defined with a keyword argument 'step' defaulting to 1.
|
|
- since step=1, the disconnect-handler calls session_disconnect()
|
|
- session_disconnect() removes session, then calls session.at_disconnect()
|
|
- session.at_disconnect() calls the custom disconnect-handler with
|
|
step=2 as argument
|
|
- since step=2, the disconnect-handler closes the connection and
|
|
performs all needed protocol cleanup.
|
|
|
|
The process of manual disconnect:
|
|
- The command/outside function calls session.session_disconnect().
|
|
- from here the process proceeds as the automatic disconnect above.
|
|
|
|
"""
|
|
|
|
import time
|
|
from datetime import datetime
|
|
#from django.contrib.auth.models import User
|
|
from django.conf import settings
|
|
#from src.objects.models import ObjectDB
|
|
from src.comms.models import Channel
|
|
from src.utils import logger, reloads
|
|
from src.commands import cmdhandler
|
|
from src.server.sessionhandler import SESSIONS
|
|
|
|
IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
|
IDLE_COMMAND = settings.IDLE_COMMAND
|
|
|
|
|
|
|
|
class IOdata(object):
|
|
"""
|
|
A simple storage object that allows for storing
|
|
new attributes on it at creation.
|
|
"""
|
|
def __init__(self, **kwargs):
|
|
"Give keyword arguments to store as new arguments on the object."
|
|
self.__dict__.update(**kwargs)
|
|
|
|
|
|
def _login(session, player):
|
|
"""
|
|
For logging a player in. Removed this from CmdConnect because ssh
|
|
wanted to call it for autologin.
|
|
"""
|
|
# We are logging in, get/setup the player object controlled by player
|
|
|
|
# Check if this is the first time the
|
|
# *player* connects (should be set by the
|
|
if player.db.FIRST_LOGIN:
|
|
player.at_first_login()
|
|
del player.db.FIRST_LOGIN
|
|
player.at_pre_login()
|
|
|
|
character = player.character
|
|
if character:
|
|
# this player has a character. Check if it's the
|
|
# first time *this character* logs in (this should be
|
|
# set by the initial create command)
|
|
if character.db.FIRST_LOGIN:
|
|
character.at_first_login()
|
|
del character.db.FIRST_LOGIN
|
|
# run character login hook
|
|
character.at_pre_login()
|
|
|
|
# actually do the login
|
|
session.session_login(player)
|
|
|
|
# post-login hooks
|
|
player.at_post_login()
|
|
if character:
|
|
character.at_post_login()
|
|
character.execute_cmd('look')
|
|
else:
|
|
player.execute_cmd('look')
|
|
|
|
|
|
#------------------------------------------------------------
|
|
# SessionBase class
|
|
#------------------------------------------------------------
|
|
|
|
class SessionBase(object):
|
|
"""
|
|
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.
|
|
|
|
"""
|
|
|
|
# use this to uniquely identify the protocol name, e.g. "telnet" or "comet"
|
|
protocol_key = "BaseProtocol"
|
|
|
|
def session_connect(self, address, suid=None):
|
|
"""
|
|
The setup of the session. An address (usually an IP address) on any form is required.
|
|
|
|
This should be called by the protocol at connection time.
|
|
|
|
suid = this is a session id. Needed by some transport protocols.
|
|
"""
|
|
self.address = address
|
|
|
|
# user setup
|
|
self.name = None
|
|
self.uid = None
|
|
self.suid = suid
|
|
self.logged_in = False
|
|
self.encoding = "utf-8"
|
|
|
|
current_time = time.time()
|
|
|
|
# The time the user last issued a command.
|
|
self.cmd_last = current_time
|
|
# Player-visible idle time, excluding the IDLE command.
|
|
self.cmd_last_visible = current_time
|
|
# The time when the user connected.
|
|
self.conn_time = current_time
|
|
# Total number of commands issued.
|
|
self.cmd_total = 0
|
|
#self.channels_subscribed = {}
|
|
SESSIONS.add_unloggedin_session(self)
|
|
# calling hook
|
|
self.at_connect()
|
|
|
|
def session_login(self, player):
|
|
"""
|
|
Startup mechanisms that need to run at login
|
|
|
|
player - the connected player
|
|
"""
|
|
# Check if this is the first time the *player* logs in
|
|
if player.db.FIRST_LOGIN:
|
|
player.at_first_login()
|
|
del player.db.FIRST_LOGIN
|
|
player.at_pre_login()
|
|
|
|
character = player.character
|
|
if character:
|
|
# this player has a character. Check if it's the
|
|
# first time *this character* logs in
|
|
if character.db.FIRST_LOGIN:
|
|
character.at_first_login()
|
|
del character.db.FIRST_LOGIN
|
|
# run character login hook
|
|
character.at_pre_login()
|
|
|
|
# actually do the login by assigning session data
|
|
|
|
self.player = player
|
|
self.user = player.user
|
|
self.uid = self.user.id
|
|
self.name = self.user.username
|
|
self.logged_in = True
|
|
self.conn_time = time.time()
|
|
|
|
# Update account's last login time.
|
|
self.user.last_login = datetime.now()
|
|
self.user.save()
|
|
self.log('Logged in: %s' % self)
|
|
|
|
# start (persistent) scripts on this object
|
|
reloads.reload_scripts(obj=self.player.character, init_mode=True)
|
|
|
|
#add session to connected list
|
|
SESSIONS.add_loggedin_session(self)
|
|
|
|
#call login hook
|
|
self.at_login(player)
|
|
|
|
# post-login hooks
|
|
player.at_post_login()
|
|
if character:
|
|
character.at_post_login()
|
|
|
|
def session_disconnect(self):
|
|
"""
|
|
Clean up the session, removing it from the game and doing some
|
|
accounting. This method is used also for non-loggedin
|
|
accounts.
|
|
|
|
Note that this methods does not close the connection - this is protocol-dependent
|
|
and have to be done right after this function!
|
|
"""
|
|
if self.logged_in:
|
|
player = self.get_player()
|
|
uaccount = player.user
|
|
uaccount.last_login = datetime.now()
|
|
uaccount.save()
|
|
self.at_disconnect()
|
|
self.logged_in = False
|
|
SESSIONS.remove_session(self)
|
|
|
|
def session_validate(self):
|
|
"""
|
|
Validate the session to make sure they have not been idle for too long
|
|
"""
|
|
if IDLE_TIMEOUT > 0 and (time.time() - self.cmd_last) > IDLE_TIMEOUT:
|
|
self.msg("Idle timeout exceeded, disconnecting.")
|
|
self.session_disconnect()
|
|
|
|
def get_player(self):
|
|
"""
|
|
Get the player associated with this session
|
|
"""
|
|
if self.logged_in:
|
|
return self.player
|
|
else:
|
|
return None
|
|
|
|
# if self.logged_in:
|
|
# character = ObjectDB.objects.get_object_with_user(self.uid)
|
|
# if not character:
|
|
# string = "No player match for session uid: %s" % self.uid
|
|
# logger.log_errmsg(string)
|
|
# return None
|
|
# return character.player
|
|
# return None
|
|
|
|
def get_character(self):
|
|
"""
|
|
Returns the in-game character associated with a session.
|
|
This returns the typeclass of the object.
|
|
"""
|
|
player = self.get_player()
|
|
if player:
|
|
return player.character
|
|
return None
|
|
|
|
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 execute_cmd(self, command_string):
|
|
"""
|
|
Execute a command string.
|
|
"""
|
|
|
|
# handle the 'idle' command
|
|
if str(command_string).strip() == IDLE_COMMAND:
|
|
self.update_session_counters(idle=True)
|
|
return
|
|
|
|
# all other inputs, including empty inputs
|
|
character = self.get_character()
|
|
|
|
if character:
|
|
# normal operation.
|
|
character.execute_cmd(command_string)
|
|
#import cProfile
|
|
#cProfile.runctx("character.execute_cmd(command_string)",
|
|
# {"command_string":command_string,"character":character}, {}, "execute_cmd.profile")
|
|
else:
|
|
if self.logged_in:
|
|
# there is no character, but we are logged in. Use player instead.
|
|
self.get_player().execute_cmd(command_string)
|
|
else:
|
|
# we are not logged in. Use special unlogged-in call.
|
|
cmdhandler.cmdhandler(self, command_string, unloggedin=True)
|
|
self.update_session_counters()
|
|
|
|
def get_data_obj(self, **kwargs):
|
|
"""
|
|
Create a data object, storing keyword arguments on itself as arguments.
|
|
"""
|
|
return IOdata(**kwargs)
|
|
|
|
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.
|
|
"""
|
|
if self.logged_in:
|
|
symbol = '#'
|
|
else:
|
|
symbol = '?'
|
|
return "<%s> %s@%s" % (symbol, self.name, self.address,)
|
|
|
|
def __unicode__(self):
|
|
"""
|
|
Unicode representation
|
|
"""
|
|
return u"%s" % str(self)
|
|
|
|
|
|
|
|
#------------------------------------------------------------
|
|
# Session class - inherit from this
|
|
#------------------------------------------------------------
|
|
|
|
class Session(SessionBase):
|
|
"""
|
|
The main class to inherit from. Overload the methods here.
|
|
"""
|
|
|
|
# exchange this for a unique name you can use to identify the
|
|
# protocol type this session uses
|
|
protocol_key = "TemplateProtocol"
|
|
|
|
#
|
|
# Hook methods
|
|
#
|
|
|
|
def at_connect(self):
|
|
"""
|
|
This method is called by the connection mechanic after
|
|
connection has been made. The session is added to the
|
|
sessionhandler and basic accounting has been made at this
|
|
point.
|
|
|
|
This is the place to put e.g. welcome screens specific to the
|
|
protocol.
|
|
"""
|
|
pass
|
|
|
|
def at_login(self, player):
|
|
"""
|
|
This method is called by the login mechanic whenever the user
|
|
has finished authenticating. The user has been moved to the
|
|
right sessionhandler list and basic book keeping has been
|
|
done at this point (so logged_in=True).
|
|
"""
|
|
pass
|
|
|
|
def at_disconnect(self):
|
|
"""
|
|
This method is called just before cleaning up the session
|
|
(so still logged_in=True at this point).
|
|
|
|
This method should not be called from commands, instead it
|
|
is called automatically by session_disconnect() as part of
|
|
the cleanup.
|
|
|
|
This method MUST call the protocol-dependant disconnect-handler
|
|
with step=2 to finalize the closing of the connection!
|
|
"""
|
|
# self.my-disconnect-handler(step=2)
|
|
pass
|
|
|
|
def at_data_in(self, string="", data=None):
|
|
"""
|
|
Player -> Evennia
|
|
"""
|
|
pass
|
|
|
|
def at_data_out(self, string="", data=None):
|
|
"""
|
|
Evennia -> Player
|
|
|
|
string - an string of any form to send to the player
|
|
data - a data structure of any form
|
|
|
|
"""
|
|
pass
|
|
|
|
# easy-access functions
|
|
def login(self, player):
|
|
"alias for at_login"
|
|
self.at_login(player)
|
|
def disconnect(self):
|
|
"alias for session_disconnect"
|
|
self.session_disconnect()
|
|
def msg(self, string='', data=None):
|
|
"alias for at_data_out"
|
|
self.at_data_out(string, data=data)
|