Guest Functionality

Allows for guest logins by entering 'connect guest' at the login screen.
Cleans up after itself but does not yet clean up stale guests from a
system crash.
This commit is contained in:
n0q 2014-07-06 16:46:02 -04:00
parent 16b1aeffc2
commit b9661c5c96
5 changed files with 182 additions and 75 deletions

View file

@ -422,7 +422,7 @@ class CmdCemit(MuxPlayerCommand):
key = "@cemit" key = "@cemit"
aliases = ["@cmsg"] aliases = ["@cmsg"]
locks = "cmd: not pperm(channel_banned)" locks = "cmd: not pperm(channel_banned) and pperm(Players)"
help_category = "Comms" help_category = "Comms"
def func(self): def func(self):
@ -498,7 +498,7 @@ class CmdChannelCreate(MuxPlayerCommand):
key = "@ccreate" key = "@ccreate"
aliases = "channelcreate" aliases = "channelcreate"
locks = "cmd:not pperm(channel_banned)" locks = "cmd:not pperm(channel_banned) and pperm(Players)"
help_category = "Comms" help_category = "Comms"
def func(self): def func(self):

View file

@ -162,7 +162,7 @@ class CmdCharCreate(MuxPlayerCommand):
if you want. if you want.
""" """
key = "@charcreate" key = "@charcreate"
locks = "cmd:all()" locks = "cmd:pperm(Players)"
help_category = "General" help_category = "General"
def func(self): def func(self):
@ -285,7 +285,7 @@ class CmdOOC(MuxPlayerCommand):
key = "@ooc" key = "@ooc"
# lock must be all(), for different puppeted objects to access it. # lock must be all(), for different puppeted objects to access it.
locks = "cmd:all()" locks = "cmd:pperm(Players)"
aliases = "@unpuppet" aliases = "@unpuppet"
help_category = "General" help_category = "General"
@ -378,11 +378,9 @@ class CmdWho(MuxPlayerCommand):
nplayers = (SESSIONS.player_count()) nplayers = (SESSIONS.player_count())
if show_session_data: if show_session_data:
# privileged info
table = prettytable.PrettyTable(["{wPlayer Name", table = prettytable.PrettyTable(["{wPlayer Name",
"{wOn for", "{wOn for",
"{wIdle", "{wIdle",
"{wPuppeting",
"{wRoom", "{wRoom",
"{wCmds", "{wCmds",
"{wProtocol", "{wProtocol",
@ -391,27 +389,25 @@ class CmdWho(MuxPlayerCommand):
if not session.logged_in: continue if not session.logged_in: continue
delta_cmd = time.time() - session.cmd_last_visible delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time delta_conn = time.time() - session.conn_time
player = session.get_player() plr_pobject = session.get_puppet()
puppet = session.get_puppet() plr_pobject = plr_pobject or session.get_player()
location = puppet.location.key if puppet else "None" table.add_row([utils.crop(plr_pobject.name, width=25),
table.add_row([utils.crop(player.name, width=25),
utils.time_format(delta_conn, 0), utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1), utils.time_format(delta_cmd, 1),
utils.crop(puppet.key if puppet else "None", width=25), hasattr(plr_pobject, "location") and plr_pobject.location and plr_pobject.location.key or "None",
utils.crop(location, width=25),
session.cmd_total, session.cmd_total,
session.protocol_key, session.protocol_key,
isinstance(session.address, tuple) and session.address[0] or session.address]) isinstance(session.address, tuple) and session.address[0] or session.address])
else: else:
# unprivileged
table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{wIdle"]) table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{wIdle"])
for session in session_list: for session in session_list:
if not session.logged_in: if not session.logged_in:
continue continue
delta_cmd = time.time() - session.cmd_last_visible delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time delta_conn = time.time() - session.conn_time
player = session.get_player() plr_pobject = session.get_puppet()
table.add_row([utils.crop(player.key, width=25), plr_pobject = plr_pobject or session.get_player()
table.add_row([utils.crop(plr_pobject.name, width=25),
utils.time_format(delta_conn, 0), utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1)]) utils.time_format(delta_cmd, 1)])
@ -491,7 +487,7 @@ class CmdPassword(MuxPlayerCommand):
Changes your password. Make sure to pick a safe one. Changes your password. Make sure to pick a safe one.
""" """
key = "@password" key = "@password"
locks = "cmd:all()" locks = "cmd:pperm(Players)"
def func(self): def func(self):
"hook function." "hook function."
@ -650,7 +646,7 @@ class CmdQuell(MuxPlayerCommand):
key = "@quell" key = "@quell"
aliases = ["@unquell"] aliases = ["@unquell"]
locks = "cmd:all()" locks = "cmd:pperm(Players)"
help_category = "General" help_category = "General"
def _recache_locks(self, player): def _recache_locks(self, player):

View file

@ -2,6 +2,7 @@
Commands that are available from the connect screen. Commands that are available from the connect screen.
""" """
import re import re
from random import getrandbits
import traceback import traceback
from django.conf import settings from django.conf import settings
from src.players.models import PlayerDB from src.players.models import PlayerDB
@ -53,14 +54,49 @@ class CmdUnconnectedConnect(MuxCommand):
other types of logged-in commands (this is because other types of logged-in commands (this is because
there is no object yet before the player has logged in) there is no object yet before the player has logged in)
""" """
session = self.caller session = self.caller
args = self.args args = self.args
# extract quoted parts # extract quoted parts
parts = [part.strip() for part in re.split(r"\"|\'", args) if part.strip()] parts = [part.strip() for part in re.split(r"\"|\'", args) if part.strip()]
if len(parts) == 1: if len(parts) == 1:
# this was (hopefully) due to no quotes being found # this was (hopefully) due to no quotes being found, or a guest login
parts = parts[0].split(None, 1) parts = parts[0].split(None, 1)
# Guest login
if len(parts) == 1 and parts[0].lower() == "guest" and settings.GUEST_LIST:
try:
# Find an available guest name.
for playername in settings.GUEST_LIST:
if not PlayerDB.objects.filter(username__iexact=playername):
break
playername = None
if playername == None:
session.msg("All guest accounts are in use. Please try again later.")
return
password = "%016x" % getrandbits(64)
home = ObjectDB.objects.get_id(settings.GUEST_HOME)
permissions = settings.PERMISSION_GUEST_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS
ptypeclass = settings.BASE_GUEST_TYPECLASS
start_location = ObjectDB.objects.get_id(settings.GUEST_START_LOCATION)
new_player = CreatePlayer(session, playername, password,
home, permissions, ptypeclass)
if new_player:
CreateCharacter(session, new_player, typeclass, start_location,
home, permissions)
session.sessionhandler.login(session, new_player)
except Exception:
# We are in the middle between logged in and -not, so we have
# to handle tracebacks ourselves at this point. If we don't,
# we won't see any errors at all.
string = "%s\nThis is a bug. Please e-mail an admin if the problem persists."
session.msg(string % (traceback.format_exc()))
logger.log_errmsg(traceback.format_exc())
finally:
return
if len(parts) != 2: if len(parts) != 2:
session.msg("\n\r Usage (without <>): connect <name> <password>") session.msg("\n\r Usage (without <>): connect <name> <password>")
return return
@ -151,6 +187,11 @@ class CmdUnconnectedCreate(MuxCommand):
# player already exists (we also ignore capitalization here) # player already exists (we also ignore capitalization here)
session.msg("Sorry, there is already a player with the name '%s'." % playername) session.msg("Sorry, there is already a player with the name '%s'." % playername)
return return
# Reserve playernames found in GUEST_LIST
if settings.GUEST_LIST and playername.lower() in map(str.lower, settings.GUEST_LIST):
string = "\n\r That name is reserved. Please choose another Playername."
session.msg(string)
return
if not re.findall('^[\w. @+-]+$', password) or not (3 < len(password)): if not re.findall('^[\w. @+-]+$', password) or not (3 < len(password)):
string = "\n\r Password should be longer than 3 characers. Letters, spaces, digits and @\.\+\-\_ only." string = "\n\r Password should be longer than 3 characers. Letters, spaces, digits and @\.\+\-\_ only."
string += "\nFor best security, make it longer than 8 characters. You can also use a phrase of" string += "\nFor best security, make it longer than 8 characters. You can also use a phrase of"
@ -161,55 +202,14 @@ class CmdUnconnectedCreate(MuxCommand):
# everything's ok. Create the new player account. # everything's ok. Create the new player account.
try: try:
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
typeclass = settings.BASE_CHARACTER_TYPECLASS
permissions = settings.PERMISSION_PLAYER_DEFAULT permissions = settings.PERMISSION_PLAYER_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS
try: new_player = CreatePlayer(session, playername, password, default_home, permissions)
new_player = create.create_player(playername, None, password,
permissions=permissions)
except Exception, e:
session.msg("There was an error creating the default Player/Character:\n%s\n If this problem persists, contact an admin." % e)
logger.log_trace()
return
# This needs to be called so the engine knows this player is
# logging in for the first time. (so it knows to call the right
# hooks during login later)
utils.init_new_player(new_player)
# join the new player to the public channel
pchanneldef = settings.CHANNEL_PUBLIC
if pchanneldef:
pchannel = ChannelDB.objects.get_channel(pchanneldef[0])
if not pchannel.connect(new_player):
string = "New player '%s' could not connect to public channel!" % new_player.key
logger.log_errmsg(string)
if MULTISESSION_MODE < 2:
# if we only allow one character, create one with the same name as Player
# (in mode 2, the character must be created manually once logging in)
start_location = ObjectDB.objects.get_id(settings.START_LOCATION) start_location = ObjectDB.objects.get_id(settings.START_LOCATION)
if not start_location: if new_player:
start_location = default_home # fallback if MULTISESSION_MODE < 2:
CreateCharacter(session, new_player, typeclass, start_location,
new_character = create.create_object(typeclass, key=playername, default_home, permissions)
location=start_location, home=default_home,
permissions=permissions)
# set playable character list
new_player.db._playable_characters.append(new_character)
# allow only the character itself and the player to puppet this character (and Immortals).
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
(new_character.id, new_player.id))
# If no description is set, set a default description
if not new_character.db.desc:
new_character.db.desc = "This is a Player."
# We need to set this to have @ic auto-connect to this character
new_player.db._last_puppet = new_character
# tell the caller everything went well. # tell the caller everything went well.
string = "A new account '%s' was created. Welcome!" string = "A new account '%s' was created. Welcome!"
if " " in playername: if " " in playername:
@ -227,6 +227,63 @@ class CmdUnconnectedCreate(MuxCommand):
logger.log_errmsg(traceback.format_exc()) logger.log_errmsg(traceback.format_exc())
def CreatePlayer(session, playername, password,
default_home, permissions, typeclass=None):
"""
Creates a player of the specified typeclass.
"""
try:
new_player = create.create_player(playername, None, password,
permissions=permissions, typeclass=typeclass)
except Exception, e:
session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e)
logger.log_trace()
return False
# This needs to be called so the engine knows this player is
# logging in for the first time. (so it knows to call the right
# hooks during login later)
utils.init_new_player(new_player)
# join the new player to the public channel
pchanneldef = settings.CHANNEL_PUBLIC
if pchanneldef:
pchannel = ChannelDB.objects.get_channel(pchanneldef[0])
if not pchannel.connect(new_player):
string = "New player '%s' could not connect to public channel!" % new_player.key
logger.log_errmsg(string)
return new_player
def CreateCharacter(session, new_player, typeclass, start_location, home, permissions):
"""
Creates a character based on a player's name. This is meant for Guest and
MULTISESSION_MODE <2 situations.
"""
try:
if not start_location:
start_location = home # fallback
new_character = create.create_object(typeclass, key=new_player.key,
location=start_location, home=home,
permissions=permissions)
# set playable character list
new_player.db._playable_characters.append(new_character)
# allow only the character itself and the player to puppet this character (and Immortals).
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
(new_character.id, new_player.id))
# If no description is set, set a default description
if not new_character.db.desc:
new_character.db.desc = "This is a Player."
# We need to set this to have @ic auto-connect to this character
new_player.db._last_puppet = new_character
except Exception, e:
session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e)
logger.log_trace()
return False
class CmdUnconnectedQuit(MuxCommand): class CmdUnconnectedQuit(MuxCommand):
""" """
quit when in unlogged-in state quit when in unlogged-in state

View file

@ -423,3 +423,44 @@ class Player(TypeClass):
(i.e. not for a restart). (i.e. not for a restart).
""" """
pass pass
class Guest(Player):
"""
This class is used for guest logins. Unlike Players, Guests and their
characters are deleted after disconnection.
"""
print __doc__
def at_post_login(self, sessid=None):
"""
In theory, guests only have one character regardless of which
MULTISESSION_MODE we're in. They don't get a choice.
"""
self._send_to_connect_channel("{G%s connected{n" % self.key)
self.execute_cmd("@ic", sessid=sessid)
def at_disconnect(self):
"""
A Guest's characters aren't meant to linger on the server. When a
Guest disconnects, we remove its character.
"""
super(Guest, self).at_disconnect()
characters = self.db._playable_characters
for character in characters:
character.delete()
def at_server_shutdown(self):
"""
We repeat at_disconnect() here just to be on the safe side.
"""
super(Guest, self).at_server_shutdown()
characters = self.db._playable_characters
for character in characters:
character.delete()
def at_post_disconnect(self):
"""
Guests aren't meant to linger on the server, either. We need to wait
until after the Guest disconnects to delete it, though.
"""
super(Guest, self).at_post_disconnect()
self.delete()

View file

@ -287,6 +287,8 @@ CHANNEL_TYPECLASS_PATHS = ["game.gamesrc.conf", "contrib"]
# Typeclass for player objects (linked to a character) (fallback) # Typeclass for player objects (linked to a character) (fallback)
BASE_PLAYER_TYPECLASS = "src.players.player.Player" BASE_PLAYER_TYPECLASS = "src.players.player.Player"
# Typeclass for guest player objects (linked to a character)
BASE_GUEST_TYPECLASS = "src.players.player.Guest"
# Typeclass and base for all objects (fallback) # Typeclass and base for all objects (fallback)
BASE_OBJECT_TYPECLASS = "src.objects.objects.Object" BASE_OBJECT_TYPECLASS = "src.objects.objects.Object"
# Typeclass for character objects linked to a player (fallback) # Typeclass for character objects linked to a player (fallback)
@ -304,10 +306,14 @@ BASE_SCRIPT_TYPECLASS = "src.scripts.scripts.DoNothing"
# fallback if an object's normal home location is deleted. Default # fallback if an object's normal home location is deleted. Default
# is Limbo (#2). # is Limbo (#2).
DEFAULT_HOME = "#2" DEFAULT_HOME = "#2"
# The default home location used for guests.
GUEST_HOME = "#2"
# The start position for new characters. Default is Limbo (#2). # The start position for new characters. Default is Limbo (#2).
# MULTISESSION_MODE = 0, 1 - used by default unloggedin create command # MULTISESSION_MODE = 0, 1 - used by default unloggedin create command
# MULTISESSION_MODE = 2 - used by default character_create command # MULTISESSION_MODE = 2 - used by default character_create command
START_LOCATION = "#2" START_LOCATION = "#2"
# The start position used for guest characters.
GUEST_START_LOCATION = "#2"
# Lookups of Attributes, Tags, Nicks, Aliases can be aggressively # Lookups of Attributes, Tags, Nicks, Aliases can be aggressively
# cached to avoid repeated database hits. This often gives noticeable # cached to avoid repeated database hits. This often gives noticeable
# performance gains since they are called so often. Drawback is that # performance gains since they are called so often. Drawback is that
@ -376,6 +382,13 @@ PERMISSION_HIERARCHY = ("Players",
"Immortals") "Immortals")
# The default permission given to all new players # The default permission given to all new players
PERMISSION_PLAYER_DEFAULT = "Players" PERMISSION_PLAYER_DEFAULT = "Players"
# The permission given to guests
PERMISSION_GUEST_DEFAULT = "Guests"
# The naming convention for guest players/characters. The size of this list
# also detemines how many guests may be on the game at once. The default is
# a maximum of five guests, named Guest1 through Guest5.
# Set to None to disable guest logins entirely.
GUEST_LIST = ["Guest" + str(s+1) for s in range(5)]
###################################################################### ######################################################################
# In-game Channels created from server start # In-game Channels created from server start