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"
aliases = ["@cmsg"]
locks = "cmd: not pperm(channel_banned)"
locks = "cmd: not pperm(channel_banned) and pperm(Players)"
help_category = "Comms"
def func(self):
@ -498,7 +498,7 @@ class CmdChannelCreate(MuxPlayerCommand):
key = "@ccreate"
aliases = "channelcreate"
locks = "cmd:not pperm(channel_banned)"
locks = "cmd:not pperm(channel_banned) and pperm(Players)"
help_category = "Comms"
def func(self):

View file

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

View file

@ -2,6 +2,7 @@
Commands that are available from the connect screen.
"""
import re
from random import getrandbits
import traceback
from django.conf import settings
from src.players.models import PlayerDB
@ -53,14 +54,49 @@ class CmdUnconnectedConnect(MuxCommand):
other types of logged-in commands (this is because
there is no object yet before the player has logged in)
"""
session = self.caller
args = self.args
# extract quoted parts
parts = [part.strip() for part in re.split(r"\"|\'", args) if part.strip()]
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)
# 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:
session.msg("\n\r Usage (without <>): connect <name> <password>")
return
@ -151,6 +187,11 @@ class CmdUnconnectedCreate(MuxCommand):
# player already exists (we also ignore capitalization here)
session.msg("Sorry, there is already a player with the name '%s'." % playername)
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)):
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"
@ -161,63 +202,22 @@ class CmdUnconnectedCreate(MuxCommand):
# everything's ok. Create the new player account.
try:
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
typeclass = settings.BASE_CHARACTER_TYPECLASS
permissions = settings.PERMISSION_PLAYER_DEFAULT
try:
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)
if not start_location:
start_location = default_home # fallback
new_character = create.create_object(typeclass, key=playername,
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.
string = "A new account '%s' was created. Welcome!"
if " " in playername:
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
else:
string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (playername, playername))
typeclass = settings.BASE_CHARACTER_TYPECLASS
new_player = CreatePlayer(session, playername, password, default_home, permissions)
start_location = ObjectDB.objects.get_id(settings.START_LOCATION)
if new_player:
if MULTISESSION_MODE < 2:
CreateCharacter(session, new_player, typeclass, start_location,
default_home, permissions)
# tell the caller everything went well.
string = "A new account '%s' was created. Welcome!"
if " " in playername:
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
else:
string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (playername, playername))
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,
@ -225,6 +225,63 @@ class CmdUnconnectedCreate(MuxCommand):
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())
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):

View file

@ -423,3 +423,44 @@ class Player(TypeClass):
(i.e. not for a restart).
"""
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)
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)
BASE_OBJECT_TYPECLASS = "src.objects.objects.Object"
# 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
# is Limbo (#2).
DEFAULT_HOME = "#2"
# The default home location used for guests.
GUEST_HOME = "#2"
# The start position for new characters. Default is Limbo (#2).
# MULTISESSION_MODE = 0, 1 - used by default unloggedin create command
# MULTISESSION_MODE = 2 - used by default character_create command
START_LOCATION = "#2"
# The start position used for guest characters.
GUEST_START_LOCATION = "#2"
# Lookups of Attributes, Tags, Nicks, Aliases can be aggressively
# cached to avoid repeated database hits. This often gives noticeable
# performance gains since they are called so often. Drawback is that
@ -376,6 +382,13 @@ PERMISSION_HIERARCHY = ("Players",
"Immortals")
# The default permission given to all new 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