Merge branch 'dev' of https://github.com/n0q/evennia into n0q-dev

This commit is contained in:
Griatch 2014-07-10 20:12:48 +02:00
commit 364b156456
6 changed files with 250 additions and 81 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"
@ -491,7 +491,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 +650,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
@ -59,8 +60,44 @@ class CmdUnconnectedConnect(MuxCommand):
# 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_ENABLED:
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 +188,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"
@ -173,55 +215,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:
@ -331,3 +332,62 @@ To login to the system, you need to do one of the following:
You can use the {wlook{n command if you want to see the connect screen again. You can use the {wlook{n command if you want to see the connect screen again.
""" """
self.caller.msg(string) self.caller.msg(string)
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

View file

@ -423,3 +423,43 @@ 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.
"""
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 filter(None, 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 filter(None, 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

@ -73,6 +73,8 @@ AMP_INTERFACE = settings.AMP_INTERFACE
WEBSERVER_PORTS = settings.WEBSERVER_PORTS WEBSERVER_PORTS = settings.WEBSERVER_PORTS
WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES
GUEST_ENABLED = settings.GUEST_ENABLED
# server-channel mappings # server-channel mappings
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
IMC2_ENABLED = settings.IMC2_ENABLED IMC2_ENABLED = settings.IMC2_ENABLED
@ -240,17 +242,16 @@ class Evennia(object):
from src.scripts.tickerhandler import TICKER_HANDLER from src.scripts.tickerhandler import TICKER_HANDLER
TICKER_HANDLER.restore() TICKER_HANDLER.restore()
if SERVER_STARTSTOP_MODULE:
# call correct server hook based on start file value # call correct server hook based on start file value
if mode in ('True', 'reload'): if mode in ('True', 'reload'):
# True was the old reload flag, kept for compatibilty # True was the old reload flag, kept for compatibilty
SERVER_STARTSTOP_MODULE.at_server_reload_start() self.at_server_reload_start()
elif mode in ('reset', 'shutdown'): elif mode in ('reset', 'shutdown'):
SERVER_STARTSTOP_MODULE.at_server_cold_start() self.at_server_cold_start()
# clear eventual lingering session storages # clear eventual lingering session storages
ObjectDB.objects.clear_all_sessids() ObjectDB.objects.clear_all_sessids()
# always call this regardless of start type # always call this regardless of start type
SERVER_STARTSTOP_MODULE.at_server_start() self.at_server_start()
def set_restart_mode(self, mode=None): def set_restart_mode(self, mode=None):
""" """
@ -316,8 +317,7 @@ class Evennia(object):
from src.scripts.tickerhandler import TICKER_HANDLER from src.scripts.tickerhandler import TICKER_HANDLER
TICKER_HANDLER.save() TICKER_HANDLER.save()
if SERVER_STARTSTOP_MODULE: self.at_server_reload_stop()
SERVER_STARTSTOP_MODULE.at_server_reload_stop()
else: else:
if mode == 'reset': if mode == 'reset':
@ -339,15 +339,13 @@ class Evennia(object):
yield ObjectDB.objects.clear_all_sessids() yield ObjectDB.objects.clear_all_sessids()
ServerConfig.objects.conf("server_restart_mode", "reset") ServerConfig.objects.conf("server_restart_mode", "reset")
if SERVER_STARTSTOP_MODULE: self.at_server_cold_stop()
SERVER_STARTSTOP_MODULE.at_server_cold_stop()
# stopping time # stopping time
from src.utils import gametime from src.utils import gametime
gametime.save() gametime.save()
if SERVER_STARTSTOP_MODULE: self.at_server_stop()
SERVER_STARTSTOP_MODULE.at_server_stop()
# if _reactor_stopping is true, reactor does not need to # if _reactor_stopping is true, reactor does not need to
# be stopped again. # be stopped again.
if os.name == 'nt' and os.path.exists(SERVER_PIDFILE): if os.name == 'nt' and os.path.exists(SERVER_PIDFILE):
@ -359,6 +357,62 @@ class Evennia(object):
self.shutdown_complete = True self.shutdown_complete = True
reactor.callLater(0, reactor.stop) reactor.callLater(0, reactor.stop)
# server start/stop hooks
def at_server_start(self):
"""
This is called every time the server starts up, regardless of
how it was shut down.
"""
if SERVER_STARTSTOP_MODULE:
SERVER_STARTSTOP_MODULE.at_server_start()
def at_server_stop(self):
"""
This is called just before a server is shut down, regardless
of it is fore a reload, reset or shutdown.
"""
if SERVER_STARTSTOP_MODULE:
SERVER_STARTSTOP_MODULE.at_server_stop()
def at_server_reload_start(self):
"""
This is called only when server starts back up after a reload.
"""
if SERVER_STARTSTOP_MODULE:
SERVER_STARTSTOP_MODULE.at_server_reload_start()
def at_server_reload_stop(self):
"""
This is called only time the server stops before a reload.
"""
if SERVER_STARTSTOP_MODULE:
SERVER_STARTSTOP_MODULE.at_server_reload_stop()
def at_server_cold_start(self):
"""
This is called only when the server starts "cold", i.e. after a
shutdown or a reset.
"""
if GUEST_ENABLED:
for guest in PlayerDB.objects.all().filter(db_typeclass_path=settings.BASE_GUEST_TYPECLASS):
for character in filter(None, guest.db._playable_characters):
character.delete()
guest.delete()
if SERVER_STARTSTOP_MODULE:
SERVER_STARTSTOP_MODULE.at_server_cold_start()
def at_server_cold_stop(self):
"""
This is called only when the server goes down due to a shutdown or reset.
"""
if SERVER_STARTSTOP_MODULE:
SERVER_STARTSTOP_MODULE.at_server_cold_stop()
#------------------------------------------------------------ #------------------------------------------------------------
# #
# Start the Evennia game server and add all active services # Start the Evennia game server and add all active services

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,20 @@ 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"
# This enables guest logins.
GUEST_ENABLED = True
# 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"
# 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 nine guests, named Guest1 through Guest9.
GUEST_LIST = ["Guest" + str(s+1) for s in range(9)]
# 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
@ -369,13 +381,16 @@ MAX_NR_CHARACTERS = 1
# The access hiearchy, in climbing order. A higher permission in the # The access hiearchy, in climbing order. A higher permission in the
# hierarchy includes access of all levels below it. Used by the perm()/pperm() # hierarchy includes access of all levels below it. Used by the perm()/pperm()
# lock functions. # lock functions.
PERMISSION_HIERARCHY = ("Players", PERMISSION_HIERARCHY = ("Guests",
"Players",
"PlayerHelpers", "PlayerHelpers",
"Builders", "Builders",
"Wizards", "Wizards",
"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"
###################################################################### ######################################################################
# In-game Channels created from server start # In-game Channels created from server start