Moved Players over to the new proxy system, made the start-hook called by the save-signal system into at_first_save()

This commit is contained in:
Griatch 2014-12-25 14:43:43 +01:00
parent db512cbbf5
commit 9af9f94fa0
9 changed files with 465 additions and 584 deletions

View file

@ -840,7 +840,7 @@ class DefaultObject(ObjectDB):
except AttributeError:
return False
def at_instance_creation(self):
def at_first_save(self):
"""
This is called by the typeclass system whenever an instance of
this class is saved for the first time. It is a generic hook
@ -874,6 +874,7 @@ class DefaultObject(ObjectDB):
updates.append("db_destination")
if updates:
self.save(update_fields=updates)
if cdict["permissions"]:
self.permissions.add(cdict["permissions"])
if cdict["locks"]:
@ -883,6 +884,7 @@ class DefaultObject(ObjectDB):
if cdict["location"]:
cdict["location"].at_object_receive(self, None)
self.at_after_move(None)
del self._createdict
self.basetype_posthook_setup()

View file

@ -37,7 +37,7 @@ class PlayerDBManager(TypedObjectManager, UserManager):
get_player_from_uid
get_player_from_name
player_search (equivalent to ev.search_player)
swap_character
#swap_character
"""
def num_total_players(self):
@ -123,33 +123,33 @@ class PlayerDBManager(TypedObjectManager, UserManager):
else:
return self.filter(username__icontains=ostring)
def swap_character(self, player, new_character, delete_old_character=False):
"""
This disconnects a player from the current character (if any) and
connects to a new character object.
"""
if new_character.player:
# the new character is already linked to a player!
return False
# do the swap
old_character = player.character
if old_character:
old_character.player = None
try:
player.character = new_character
new_character.player = player
except Exception:
# recover old setup
if old_character:
old_character.player = player
player.character = old_character
return False
if old_character and delete_old_character:
old_character.delete()
return True
# def swap_character(self, player, new_character, delete_old_character=False):
# """
# This disconnects a player from the current character (if any) and
# connects to a new character object.
#
# """
#
# if new_character.player:
# # the new character is already linked to a player!
# return False
#
# # do the swap
# old_character = player.character
# if old_character:
# old_character.player = None
# try:
# player.character = new_character
# new_character.player = player
# except Exception:
# # recover old setup
# if old_character:
# old_character.player = player
# player.character = old_character
# return False
# if old_character and delete_old_character:
# old_character.delete()
# return True
class PlayerManager(PlayerDBManager, TypeclassManager):
pass

View file

@ -22,21 +22,14 @@ from django.contrib.auth.models import AbstractUser
from django.utils.encoding import smart_str
from src.players.manager import PlayerDBManager
from src.scripts.models import ScriptDB
from src.typeclasses.models import TypedObject
from src.commands import cmdhandler
from src.utils import utils, logger
from src.utils.utils import to_str, make_iter
from django.utils.translation import ugettext as _
from src.utils.utils import make_iter
__all__ = ("PlayerDB",)
#_ME = _("me")
#_SELF = _("self")
_SESSIONS = None
_AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
_MULTISESSION_MODE = settings.MULTISESSION_MODE
_GA = object.__getattribute__
@ -147,7 +140,7 @@ class PlayerDB(TypedObject, AbstractUser):
cmdset_storage = property(cmdset_storage_get, cmdset_storage_set, cmdset_storage_del)
#
# PlayerDB main class properties and methods
# property/field access
#
def __str__(self):
@ -182,230 +175,3 @@ class PlayerDB(TypedObject, AbstractUser):
def __uid_del(self):
raise Exception("User id cannot be deleted!")
uid = property(__uid_get, __uid_set, __uid_del)
#
# PlayerDB class access methods
#
# session-related methods
def get_session(self, sessid):
"""
Return session with given sessid connected to this player.
note that the sessionhandler also accepts sessid as an iterable.
"""
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.session_from_player(self, sessid)
def get_all_sessions(self):
"Return all sessions connected to this player"
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.sessions_from_player(self)
sessions = property(get_all_sessions) # alias shortcut
def disconnect_session_from_player(self, sessid):
"""
Access method for disconnecting a given session from the player
(connection happens automatically in the sessionhandler)
"""
# this should only be one value, loop just to make sure to
# clean everything
sessions = (session for session in self.get_all_sessions()
if session.sessid == sessid)
for session in sessions:
# this will also trigger unpuppeting
session.sessionhandler.disconnect(session)
# puppeting operations
def puppet_object(self, sessid, obj, normal_mode=True):
"""
Use the given session to control (puppet) the given object (usually
a Character type). Note that we make no puppet checks here, that must
have been done before calling this method.
sessid - session id of session to connect
obj - the object to connect to
normal_mode - trigger hooks and extra checks - this is turned off when
the server reloads, to quickly re-connect puppets.
returns True if successful, False otherwise
"""
session = self.get_session(sessid)
if not session:
return False
if normal_mode and session.puppet:
# cleanly unpuppet eventual previous object puppeted by this session
self.unpuppet_object(sessid)
if obj.player and obj.player.is_connected and obj.player != self:
# we don't allow to puppet an object already controlled by an active
# player. To kick a player, call unpuppet_object on them explicitly.
return
# if we get to this point the character is ready to puppet or it
# was left with a lingering player/sessid reference from an unclean
# server kill or similar
if normal_mode:
obj.at_pre_puppet(self, sessid=sessid)
# do the connection
obj.sessid.add(sessid)
obj.player = self
session.puid = obj.id
session.puppet = obj
# validate/start persistent scripts on object
ScriptDB.objects.validate(obj=obj)
if normal_mode:
obj.at_post_puppet()
return True
def unpuppet_object(self, sessid):
"""
Disengage control over an object
sessid - the session id to disengage
returns True if successful
"""
session = self.get_session(sessid)
if not session:
return False
obj = hasattr(session, "puppet") and session.puppet or None
if not obj:
return False
# do the disconnect, but only if we are the last session to puppet
obj.at_pre_unpuppet()
obj.dbobj.sessid.remove(sessid)
if not obj.dbobj.sessid.count():
del obj.dbobj.player
obj.at_post_unpuppet(self, sessid=sessid)
session.puppet = None
session.puid = None
return True
def unpuppet_all(self):
"""
Disconnect all puppets. This is called by server
before a reset/shutdown.
"""
for session in self.get_all_sessions():
self.unpuppet_object(session.sessid)
def get_puppet(self, sessid, return_dbobj=False):
"""
Get an object puppeted by this session through this player. This is
the main method for retrieving the puppeted object from the
player's end.
sessid - return character connected to this sessid,
character - return character if connected to this player, else None.
"""
session = self.get_session(sessid)
if not session:
return None
if return_dbobj:
return session.puppet
return session.puppet and session.puppet or None
def get_all_puppets(self, return_dbobj=False):
"""
Get all currently puppeted objects as a list
"""
puppets = [session.puppet for session in self.get_all_sessions()
if session.puppet]
if return_dbobj:
return puppets
return [puppet for puppet in puppets]
def __get_single_puppet(self):
"""
This is a legacy convenience link for users of
MULTISESSION_MODE 0 or 1. It will return
only the first puppet. For mode 2, this returns
a list of all characters.
"""
puppets = self.get_all_puppets()
if _MULTISESSION_MODE in (0, 1):
return puppets and puppets[0] or None
return puppets
character = property(__get_single_puppet)
puppet = property(__get_single_puppet)
# utility methods
def delete(self, *args, **kwargs):
"""
Deletes the player permanently.
"""
for session in self.get_all_sessions():
# unpuppeting all objects and disconnecting the user, if any
# sessions remain (should usually be handled from the
# deleting command)
self.unpuppet_object(session.sessid)
session.sessionhandler.disconnect(session, reason=_("Player being deleted."))
self.scripts.stop()
_GA(self, "attributes").clear()
_GA(self, "nicks").clear()
_GA(self, "aliases").clear()
super(PlayerDB, self).delete(*args, **kwargs)
def execute_cmd(self, raw_string, sessid=None, **kwargs):
"""
Do something as this player. This method is never called normally,
but only when the player object itself is supposed to execute the
command. It takes player nicks into account, but not nicks of
eventual puppets.
raw_string - raw command input coming from the command line.
sessid - the optional session id to be responsible for the command-send
**kwargs - other keyword arguments will be added to the found command
object instace as variables before it executes. This is
unused by default Evennia but may be used to set flags and
change operating paramaters for commands at run-time.
"""
raw_string = utils.to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string,
categories=("inputline", "channel"), include_player=False)
if not sessid and _MULTISESSION_MODE in (0, 1):
# in this case, we should either have only one sessid, or the sessid
# should not matter (since the return goes to all of them we can
# just use the first one as the source)
try:
sessid = self.get_all_sessions()[0].sessid
except IndexError:
# this can happen for bots
sessid = None
return cmdhandler.cmdhandler(self, raw_string,
callertype="player", sessid=sessid, **kwargs)
def search(self, searchdata, return_puppet=False, **kwargs):
"""
This is similar to the ObjectDB search method but will search for
Players only. Errors will be echoed, and None returned if no Player
is found.
searchdata - search criterion, the Player's key or dbref to search for
return_puppet - will try to return the object the player controls
instead of the Player object itself. If no
puppeted object exists (since Player is OOC), None will
be returned.
Extra keywords are ignored, but are allowed in call in order to make
API more consistent with objects.models.TypedObject.search.
"""
#TODO deprecation
if "return_character" in kwargs:
logger.log_depmsg("Player.search's 'return_character' keyword is deprecated. Use the return_puppet keyword instead.")
return_puppet = kwargs.get("return_character")
matches = _GA(self, "__class__").objects.player_search(searchdata)
matches = _AT_SEARCH_RESULT(self, searchdata, matches, global_search=True)
if matches and return_puppet:
try:
return _GA(matches, "puppet")
except AttributeError:
return None
return matches

View file

@ -17,15 +17,23 @@ from src.typeclasses.models import TypeclassBase
from src.players.manager import PlayerManager
from src.players.models import PlayerDB
from src.comms.models import ChannelDB
from src.commands import cmdhandler
from src.scripts.models import ScriptDB
from src.utils import logger
from src.utils.utils import lazy_property, to_str, make_iter
from src.utils.utils import (lazy_property, to_str,
make_iter, to_unicode,
variable_from_module)
from src.typeclasses.attributes import NickHandler
from src.scripts.scripthandler import ScriptHandler
from src.commands.cmdsethandler import CmdSetHandler
from django.utils.translation import ugettext as _
__all__ = ("DefaultPlayer",)
_SESSIONS = None
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
_MULTISESSION_MODE = settings.MULTISESSION_MODE
_CMDSET_PLAYER = settings.CMDSET_PLAYER
_CONNECT_CHANNEL = None
@ -74,7 +82,7 @@ class DefaultPlayer(PlayerDB):
* Helper methods
msg(outgoing_string, from_obj=None, **kwargs)
swap_character(new_character, delete_old_character=False)
#swap_character(new_character, delete_old_character=False)
execute_cmd(raw_string)
search(ostring, global_search=False, attribute_name=None,
use_nicks=False, location=None,
@ -122,6 +130,171 @@ class DefaultPlayer(PlayerDB):
return NickHandler(self)
# session-related methods
def get_session(self, sessid):
"""
Return session with given sessid connected to this player.
note that the sessionhandler also accepts sessid as an iterable.
"""
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.session_from_player(self, sessid)
def get_all_sessions(self):
"Return all sessions connected to this player"
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.sessions_from_player(self)
sessions = property(get_all_sessions) # alias shortcut
def disconnect_session_from_player(self, sessid):
"""
Access method for disconnecting a given session from the player
(connection happens automatically in the sessionhandler)
"""
# this should only be one value, loop just to make sure to
# clean everything
sessions = (session for session in self.get_all_sessions()
if session.sessid == sessid)
for session in sessions:
# this will also trigger unpuppeting
session.sessionhandler.disconnect(session)
# puppeting operations
def puppet_object(self, sessid, obj, normal_mode=True):
"""
Use the given session to control (puppet) the given object (usually
a Character type). Note that we make no puppet checks here, that must
have been done before calling this method.
sessid - session id of session to connect
obj - the object to connect to
normal_mode - trigger hooks and extra checks - this is turned off when
the server reloads, to quickly re-connect puppets.
returns True if successful, False otherwise
"""
session = self.get_session(sessid)
if not session:
return False
if normal_mode and session.puppet:
# cleanly unpuppet eventual previous object puppeted by this session
self.unpuppet_object(sessid)
if obj.player and obj.player.is_connected and obj.player != self:
# we don't allow to puppet an object already controlled by an active
# player. To kick a player, call unpuppet_object on them explicitly.
return
# if we get to this point the character is ready to puppet or it
# was left with a lingering player/sessid reference from an unclean
# server kill or similar
if normal_mode:
obj.at_pre_puppet(self, sessid=sessid)
# do the connection
obj.sessid.add(sessid)
obj.player = self
session.puid = obj.id
session.puppet = obj
# validate/start persistent scripts on object
ScriptDB.objects.validate(obj=obj)
if normal_mode:
obj.at_post_puppet()
return True
def unpuppet_object(self, sessid):
"""
Disengage control over an object
sessid - the session id to disengage
returns True if successful
"""
session = self.get_session(sessid)
if not session:
return False
obj = hasattr(session, "puppet") and session.puppet or None
if not obj:
return False
# do the disconnect, but only if we are the last session to puppet
obj.at_pre_unpuppet()
obj.dbobj.sessid.remove(sessid)
if not obj.dbobj.sessid.count():
del obj.dbobj.player
obj.at_post_unpuppet(self, sessid=sessid)
session.puppet = None
session.puid = None
return True
def unpuppet_all(self):
"""
Disconnect all puppets. This is called by server
before a reset/shutdown.
"""
for session in self.get_all_sessions():
self.unpuppet_object(session.sessid)
def get_puppet(self, sessid, return_dbobj=False):
"""
Get an object puppeted by this session through this player. This is
the main method for retrieving the puppeted object from the
player's end.
sessid - return character connected to this sessid,
character - return character if connected to this player, else None.
"""
session = self.get_session(sessid)
if not session:
return None
if return_dbobj:
return session.puppet
return session.puppet and session.puppet or None
def get_all_puppets(self, return_dbobj=False):
"""
Get all currently puppeted objects as a list
"""
puppets = [session.puppet for session in self.get_all_sessions()
if session.puppet]
if return_dbobj:
return puppets
return [puppet for puppet in puppets]
def __get_single_puppet(self):
"""
This is a legacy convenience link for users of
MULTISESSION_MODE 0 or 1. It will return
only the first puppet. For mode 2, this returns
a list of all characters.
"""
puppets = self.get_all_puppets()
if _MULTISESSION_MODE in (0, 1):
return puppets and puppets[0] or None
return puppets
character = property(__get_single_puppet)
puppet = property(__get_single_puppet)
# utility methods
def delete(self, *args, **kwargs):
"""
Deletes the player permanently.
"""
for session in self.get_all_sessions():
# unpuppeting all objects and disconnecting the user, if any
# sessions remain (should usually be handled from the
# deleting command)
self.unpuppet_object(session.sessid)
session.sessionhandler.disconnect(session, reason=_("Player being deleted."))
self.scripts.stop()
self.attributes.clear()
self.nicks.clear()
self.aliases.clear()
super(PlayerDB, self).delete(*args, **kwargs)
## methods inherited from database model
def msg(self, text=None, from_obj=None, sessid=None, **kwargs):
@ -167,48 +340,38 @@ class DefaultPlayer(PlayerDB):
for sess in self.get_all_sessions():
sess.msg(text=text, **kwargs)
def swap_character(self, new_character, delete_old_character=False):
"""
Swaps the character controlled by this Player, if possible.
new_character (Object) - character/object to swap to
delete_old_character (bool) - delete the old character when swapping
Returns: True/False depending on if swap suceeded or not.
"""
return super(DefaultPlayer, self).swap_character(new_character, delete_old_character=delete_old_character)
def execute_cmd(self, raw_string, sessid=None, **kwargs):
"""
Do something as this object. This command transparently
lets its typeclass execute the command. This method
is -not- called by Evennia normally, it is here to be
called explicitly in code.
Do something as this player. This method is never called normally,
but only when the player object itself is supposed to execute the
command. It takes player nicks into account, but not nicks of
eventual puppets.
Argument:
raw_string (string) - raw command input
sessid (int) - id of session executing the command. This sets the
sessid property on the command
raw_string - raw command input coming from the command line.
sessid - the optional session id to be responsible for the command-send
**kwargs - other keyword arguments will be added to the found command
object instace as variables before it executes. This is
unused by default Evennia but may be used to set flags and
change operating paramaters for commands at run-time.
Returns Deferred - this is an asynchronous Twisted object that will
not fire until the command has actually finished executing. To
overload this one needs to attach callback functions to it, with
addCallback(function). This function will be called with an
eventual return value from the command execution.
This return is not used at all by Evennia by default, but might
be useful for coders intending to implement some sort of nested
command structure.
"""
return super(DefaultPlayer, self).execute_cmd(raw_string, sessid=sessid, **kwargs)
raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string,
categories=("inputline", "channel"), include_player=False)
if not sessid and _MULTISESSION_MODE in (0, 1):
# in this case, we should either have only one sessid, or the sessid
# should not matter (since the return goes to all of them we can
# just use the first one as the source)
try:
sessid = self.get_all_sessions()[0].sessid
except IndexError:
# this can happen for bots
sessid = None
return cmdhandler.cmdhandler(self, raw_string,
callertype="player", sessid=sessid, **kwargs)
def search(self, searchdata, return_puppet=False, **kwargs):
"""
This is similar to the Object search method but will search for
This is similar to the ObjectDB search method but will search for
Players only. Errors will be echoed, and None returned if no Player
is found.
searchdata - search criterion, the Player's key or dbref to search for
@ -224,7 +387,14 @@ class DefaultPlayer(PlayerDB):
# handle wrapping of common terms
if searchdata.lower() in ("me", "*me", "self", "*self",):
return self
return super(DefaultPlayer, self).search(searchdata, return_puppet=return_puppet, **kwargs)
matches = self.__class__.objects.player_search(searchdata)
matches = _AT_SEARCH_RESULT(self, searchdata, matches, global_search=True)
if matches and return_puppet:
try:
return matches.puppet
except AttributeError:
return None
return matches
def is_typeclass(self, typeclass, exact=False):
"""
@ -332,6 +502,7 @@ class DefaultPlayer(PlayerDB):
lockstring = "attrread:perm(Admins);attredit:perm(Admins);attrcreate:perm(Admins)"
self.attributes.add("_playable_characters", [], lockstring=lockstring)
# TODO - handle this in __init__ instead.
def at_init(self):
"""
This is always called whenever this object is initiated --
@ -344,12 +515,35 @@ class DefaultPlayer(PlayerDB):
"""
pass
# Note that the hooks below also exist in the character object's
# typeclass. You can often ignore these and rely on the character
# ones instead, unless you are implementing a multi-character game
# and have some things that should be done regardless of which
# character is currently connected to this player.
def at_first_save(self):
"""
This is a generic hook called by Evennia when this object is
saved to the database the very first time. You generally
don't override this method but the hooks called by it.
"""
self.basetype_setup()
self.at_player_creation()
permissions = settings.PERMISSION_PLAYER_DEFAULT
if hasattr(self, "_createdict"):
# this will only be set if the utils.create_player
# function was used to create the object.
cdict = self._createdict
if "locks" in cdict:
self.locks.add(cdict["locks"])
if "permissions" in cdict:
permissions = cdict["permissions"]
del self._createdict
self.permissions.add(permissions)
def at_access(self, result, accessing_obj, access_type, **kwargs):
"""
This is called with the result of an access call, along with
@ -373,8 +567,7 @@ class DefaultPlayer(PlayerDB):
def at_first_login(self):
"""
Only called once, the very first
time the user logs in.
Called the very first time this player logs into the game.
"""
pass
@ -472,6 +665,7 @@ class DefaultPlayer(PlayerDB):
"""
pass
class Guest(DefaultPlayer):
"""
This class is used for guest logins. Unlike Players, Guests and their

View file

@ -158,23 +158,23 @@ class ScriptDB(TypedObject):
object = property(__get_obj, __set_obj)
def at_typeclass_error(self):
"""
If this is called, it means the typeclass has a critical
error and cannot even be loaded. We don't allow a script
to be created under those circumstances. Already created,
permanent scripts are set to already be active so they
won't get activated now (next reboot the bug might be fixed)
"""
# By setting is_active=True, we trick the script not to run "again".
self.is_active = True
return super(ScriptDB, self).at_typeclass_error()
delete_iter = 0
def delete(self):
"Delete script"
if self.delete_iter > 0:
return
self.delete_iter += 1
_GA(self, "attributes").clear()
super(ScriptDB, self).delete()
# def at_typeclass_error(self):
# """
# If this is called, it means the typeclass has a critical
# error and cannot even be loaded. We don't allow a script
# to be created under those circumstances. Already created,
# permanent scripts are set to already be active so they
# won't get activated now (next reboot the bug might be fixed)
# """
# # By setting is_active=True, we trick the script not to run "again".
# self.is_active = True
# return super(ScriptDB, self).at_typeclass_error()
#
# delete_iter = 0
# def delete(self):
# "Delete script"
# if self.delete_iter > 0:
# return
# self.delete_iter += 1
# _GA(self, "attributes").clear()
# super(ScriptDB, self).delete()

View file

@ -351,109 +351,146 @@ class ScriptBase(ScriptDB):
class Script(ScriptBase):
"""
This is the class you should inherit from, it implements
the hooks called by the script machinery.
This is the base TypeClass for all Scripts. Scripts describe events,
timers and states in game, they can have a time component or describe
a state that changes under certain conditions.
Script API:
* Available properties (only available on initiated Typeclass objects)
key (string) - name of object
name (string)- same as key
aliases (list of strings) - aliases to the object. Will be saved to
database as AliasDB entries but returned as strings.
dbref (int, read-only) - unique #id-number. Also "id" can be used.
dbobj (Object, read-only) - link to database model. dbobj.typeclass
points back to this class
typeclass (Object, read-only) - this links back to this class as an
identified only. Use self.swap_typeclass() to switch.
date_created (string) - time stamp of object creation
permissions (list of strings) - list of permission strings
desc (string) - optional description of script, shown in listings
obj (Object) - optional object that this script is connected to
and acts on (set automatically
by obj.scripts.add())
interval (int) - how often script should run, in seconds.
<=0 turns off ticker
start_delay (bool) - if the script should start repeating right
away or wait self.interval seconds
repeats (int) - how many times the script should repeat before
stopping. <=0 means infinite repeats
persistent (bool) - if script should survive a server shutdown or not
is_active (bool) - if script is currently running
* Handlers
locks - lock-handler: use locks.add() to add new lock strings
db - attribute-handler: store/retrieve database attributes on this
self.db.myattr=val, val=self.db.myattr
ndb - non-persistent attribute handler: same as db but does not
create a database entry when storing data
* Helper methods
start() - start script (this usually happens automatically at creation
and obj.script.add() etc)
stop() - stop script, and delete it
pause() - put the script on hold, until unpause() is called. If script
is persistent, the pause state will survive a shutdown.
unpause() - restart a previously paused script. The script will
continue as if it was never paused.
force_repeat() - force-step the script, regardless of how much remains
until next step. This counts like a normal firing in all ways.
time_until_next_repeat() - if a timed script (interval>0), returns
time until next tick
remaining_repeats() - number of repeats remaining, if limited
* Hook methods
at_script_creation() - called only once, when an object of this
class is first created.
is_valid() - is called to check if the script is valid to be running
at the current time. If is_valid() returns False, the
running script is stopped and removed from the game. You
can use this to check state changes (i.e. an script
tracking some combat stats at regular intervals is only
valid to run while there is actual combat going on).
at_start() - Called every time the script is started, which for
persistent scripts is at least once every server start.
Note that this is unaffected by self.delay_start, which
only delays the first call to at_repeat(). It will also
be called after a pause, to allow for setting up the script.
at_repeat() - Called every self.interval seconds. It will be called
immediately upon launch unless self.delay_start is True,
which will delay the first call of this method by
self.interval seconds. If self.interval<=0, this method
will never be called.
at_stop() - Called as the script object is stopped and is about to
be removed from the game, e.g. because is_valid()
returned False or self.stop() was called manually.
at_server_reload() - Called when server reloads. Can be used to save
temporary variables you want should survive a reload.
at_server_shutdown() - called at a full server shutdown.
"""
def __init__(self, *args, **kwargs):
def at_first_save(self):
"""
This is the base TypeClass for all Scripts. Scripts describe events,
timers and states in game, they can have a time component or describe
a state that changes under certain conditions.
This is called after very first time this object is saved.
Generally, you don't need to overload this, but only the hooks
called by this method.
"""
self.at_script_creation(self)
Script API:
if hasattr(self, "_createdict"):
# this will only be set if the utils.create_script
# function was used to create the object. We want
# the create call's kwargs to override the values
# set by hooks.
cdict = self._createdict
updates = []
if not cdict["key"]:
self.db_key = "#%i" % self.dbid
updates.append("db_key")
elif self.key != cdict["db_key"]:
self.db_key = cdict["key"]
updates.append("db_key")
if cdict["interval"] and self.interval != cdict["interval"]:
self.db_interval = cdict["interval"]
updates.append("db_interval")
if cdict["start_delay"] and self.start_delay != cdict["start_delay"]:
self.db_start_delay = cdict["start_delay"]
updates.append("db_start_delay")
if cdict["repeats"] and self.repeats != cdict["repeats"]:
self.db_repeats = cdict["repeats"]
updates.append("db_repeats")
if cdict["persistent"] and self.persistent != cdict["persistent"]:
self.db_persistent = cdict["persistent"]
updates.append("db_persistent")
if updates:
self.save(update_fields=updates)
* Available properties (only available on initiated Typeclass objects)
if cdict["permissions"]:
self.permissions.add(cdict["permissions"])
if cdict["locks"]:
self.locks.add(cdict["locks"])
if cdict["aliases"]:
self.aliases.add(cdict["aliases"])
key (string) - name of object
name (string)- same as key
aliases (list of strings) - aliases to the object. Will be saved to
database as AliasDB entries but returned as strings.
dbref (int, read-only) - unique #id-number. Also "id" can be used.
dbobj (Object, read-only) - link to database model. dbobj.typeclass
points back to this class
typeclass (Object, read-only) - this links back to this class as an
identified only. Use self.swap_typeclass() to switch.
date_created (string) - time stamp of object creation
permissions (list of strings) - list of permission strings
if not cdict["autostart"]:
# don't auto-start the script
return
desc (string) - optional description of script, shown in listings
obj (Object) - optional object that this script is connected to
and acts on (set automatically
by obj.scripts.add())
interval (int) - how often script should run, in seconds.
<=0 turns off ticker
start_delay (bool) - if the script should start repeating right
away or wait self.interval seconds
repeats (int) - how many times the script should repeat before
stopping. <=0 means infinite repeats
persistent (bool) - if script should survive a server shutdown or not
is_active (bool) - if script is currently running
# auto-start script (default)
self.start()
* Handlers
locks - lock-handler: use locks.add() to add new lock strings
db - attribute-handler: store/retrieve database attributes on this
self.db.myattr=val, val=self.db.myattr
ndb - non-persistent attribute handler: same as db but does not
create a database entry when storing data
* Helper methods
start() - start script (this usually happens automatically at creation
and obj.script.add() etc)
stop() - stop script, and delete it
pause() - put the script on hold, until unpause() is called. If script
is persistent, the pause state will survive a shutdown.
unpause() - restart a previously paused script. The script will
continue as if it was never paused.
force_repeat() - force-step the script, regardless of how much remains
until next step. This counts like a normal firing in all ways.
time_until_next_repeat() - if a timed script (interval>0), returns
time until next tick
remaining_repeats() - number of repeats remaining, if limited
* Hook methods
at_script_creation() - called only once, when an object of this
class is first created.
is_valid() - is called to check if the script is valid to be running
at the current time. If is_valid() returns False, the
running script is stopped and removed from the game. You
can use this to check state changes (i.e. an script
tracking some combat stats at regular intervals is only
valid to run while there is actual combat going on).
at_start() - Called every time the script is started, which for
persistent scripts is at least once every server start.
Note that this is unaffected by self.delay_start, which
only delays the first call to at_repeat(). It will also
be called after a pause, to allow for setting up the script.
at_repeat() - Called every self.interval seconds. It will be called
immediately upon launch unless self.delay_start is True,
which will delay the first call of this method by
self.interval seconds. If self.interval<=0, this method
will never be called.
at_stop() - Called as the script object is stopped and is about to
be removed from the game, e.g. because is_valid()
returned False or self.stop() was called manually.
at_server_reload() - Called when server reloads. Can be used to save
temporary variables you want should survive a reload.
at_server_shutdown() - called at a full server shutdown.
"""
super(Script, self).__init__(*args, **kwargs)
def at_script_creation(self):
"""
Only called once, by the create function.
"""
self.key = "<unnamed>"
self.desc = ""
self.interval = 0 # infinite
self.start_delay = False
self.repeats = 0 # infinite
self.persistent = False
pass
def is_valid(self):
"""

View file

@ -74,10 +74,10 @@ _SA = object.__setattr__
# signal receivers. Assigned in __new__
def post_save(sender, instance, created, **kwargs):
"""
Is called Receive a signal just after the object is saved.
Receives a signal just after the object is saved.
"""
if created:
instance.at_instance_creation()
instance.at_first_save()
#TODO - put OOB handler here?
@ -363,7 +363,8 @@ class TypedObject(SharedMemoryModel):
self.nattributes.clear()
if run_start_hooks:
self.at_instance_creation()
# fake this call to mimic the first save
self.at_first_save()
#
# Lock / permission methods

View file

@ -48,33 +48,6 @@ __all__ = ("create_object", "create_script", "create_help_entry",
_GA = object.__getattribute__
# Helper function
def handle_dbref(inp, objclass, raise_errors=True):
"""
Convert a #dbid to a valid object of objclass. objclass
should be a valid object class to filter against (objclass.filter ...)
If not raise_errors is set, this will swallow errors of non-existing
objects.
"""
if not (isinstance(inp, basestring) and inp.startswith("#")):
return inp
# a string, analyze it
inp = inp.lstrip('#')
try:
if int(inp) < 0:
return None
except ValueError:
return None
# if we get to this point, inp is an integer dbref; get the matching object
try:
return objclass.objects.get(id=inp)
except Exception:
if raise_errors:
raise
return inp
#
# Game Object creation
#
@ -128,7 +101,7 @@ def create_object(typeclass=None, key=None, location=None,
"locks":locks, "aliases":aliases, "destination":destination,
"report_to":report_to, "nohome":nohome}
# this will trigger the save signal which in turn calls the
# at_instance_creation hook on the typeclass, where the _createdict can be
# at_first_save hook on the typeclass, where the _createdict can be
# used.
new_object.save()
return new_object
@ -168,83 +141,33 @@ def create_script(typeclass, key=None, obj=None, player=None, locks=None,
error will be raised. If set, this method will
return None upon errors.
"""
global _Script, _ScriptDB
if not _Script:
from src.scripts.scripts import Script as _Script
if not _ScriptDB:
from src.scripts.models import ScriptDB as _ScriptDB
typeclass = typeclass if typeclass else settings.BASE_SCRIPT_TYPECLASS
if not typeclass:
typeclass = settings.BASE_SCRIPT_TYPECLASS
elif isinstance(typeclass, _ScriptDB):
# this is already an scriptdb instance, extract its typeclass
typeclass = typeclass.typeclass.path
elif isinstance(typeclass, _Script) or utils.inherits_from(typeclass, _Script):
# this is already an object typeclass, extract its path
typeclass = typeclass.path
if isinstance(typeclass, basestring):
# a path is given. Load the actual typeclass
typeclass = class_from_module(typeclass, settings.SCRIPT_TYPECLASS_PATHS)
# create new database script
new_db_script = _ScriptDB()
# validate input
player = dbid_to_obj(player)
obj = dbid_to_obj(obj)
# assign the typeclass
typeclass = utils.to_unicode(typeclass)
new_db_script.typeclass_path = typeclass
# the name/key is often set later in the typeclass. This
# is set here as a failsafe.
if key:
new_db_script.key = key
else:
new_db_script.key = "#%i" % new_db_script.id
# this will either load the typeclass or the default one
new_script = new_db_script.typeclass
if not _GA(new_db_script, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still
# gave us a default
SharedMemoryModel.delete(new_db_script)
if report_to:
_GA(report_to, "msg")("Error creating %s (%s): %s" % (new_db_script.key, typeclass,
_GA(new_db_script, "typeclass_last_errmsg")))
return None
else:
raise Exception(_GA(new_db_script, "typeclass_last_errmsg"))
if obj:
new_script.obj = obj
if player:
new_script.player = player
# call the hook method. This is where all at_creation
# customization happens as the typeclass stores custom
# things on its database object.
new_script.at_script_creation()
# custom-given variables override the hook
if key:
new_script.key = key
if locks:
new_script.locks.add(locks)
if interval is not None:
new_script.interval = interval
if start_delay is not None:
new_script.start_delay = start_delay
if repeats is not None:
new_script.repeats = repeats
if persistent is not None:
new_script.persistent = persistent
# must do this before starting the script since some
# scripts may otherwise run for a very short time and
# try to delete itself before we have a time to save it.
new_db_script.save()
# a new created script should usually be started.
if autostart:
new_script.start()
# create new instance
new_script = typeclass(db_key=key, db_obj=obj, db_player=player,
db_interval=interval, db_start_delay=start_delay,
db_repeats=repeats, db_peristent=persistent)
# store the call signature for the signal
new_script._createdict = {"key":key, "obj":obj, "player":player,
"locks":locks, "interval":interval,
"start_delay":start_delay, "repeats":repeats,
"persistent":persistent, "autostart":autostart,
"report_to":report_to}
# this will trigger the save signal which in turn calls the
# at_first_save hook on the tyepclass, where the _createdict
# can be used.
new_script.save()
return new_script
#alias
script = create_script
@ -413,11 +336,16 @@ def create_player(key, email, password,
operations and is thus not suitable for play-testing the game.
"""
global _PlayerDB, _Player
if not _PlayerDB:
from src.players.models import PlayerDB as _PlayerDB
if not _Player:
from src.players.player import Player as _Player
typeclass = typeclass if typeclass else settings.BASE_PLAYER_TYPECLASS
if isinstance(typeclass, basestring):
# a path is given. Load the actual typeclass.
typeclass = class_from_module(typeclass, settings.OBJECT_TYPECLASS_PATHS)
typeclass_path = typeclass.path
# setup input for the create command. We use PlayerDB as baseclass
# here to give us maximum freedom (the typeclasses will load
# correctly when each object is recovered).
if not email:
email = "dummy@dummy.com"
@ -425,69 +353,23 @@ def create_player(key, email, password,
raise ValueError("A Player with the name '%s' already exists." % key)
# this handles a given dbref-relocate to a player.
report_to = handle_dbref(report_to, _PlayerDB)
report_to = dbid_to_obj(report_to, _PlayerDB)
try:
# create the correct player object
if is_superuser:
new_player = _PlayerDB.objects.create_superuser(key, email, password)
else:
new_player = _PlayerDB.objects.create_user(key, email, password)
new_player.db_typeclass_path = typeclass_path
# store the call signature for the signal
new_player._createdict = {"locks":locks, "permissions":permissions,
"report_to":report_to}
# create the correct Player object
if is_superuser:
new_db_player = _PlayerDB.objects.create_superuser(key, email, password)
else:
new_db_player = _PlayerDB.objects.create_user(key, email, password)
if not typeclass:
typeclass = settings.BASE_PLAYER_TYPECLASS
elif isinstance(typeclass, _PlayerDB):
# this is an PlayerDB instance, extract its typeclass path
typeclass = typeclass.typeclass.path
elif isinstance(typeclass, _Player) or utils.inherits_from(typeclass, _Player):
# this is Player object typeclass, extract its path
typeclass = typeclass.path
# assign the typeclass
typeclass = utils.to_unicode(typeclass)
new_db_player.typeclass_path = typeclass
# this will either load the typeclass or the default one
new_player = new_db_player.typeclass
if not _GA(new_db_player, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input
# and it still gave us a default
SharedMemoryModel.delete(new_db_player)
if report_to:
_GA(report_to, "msg")("Error creating %s (%s):\n%s" % (new_db_player.key, typeclass,
_GA(new_db_player, "typeclass_last_errmsg")))
return None
else:
raise Exception(_GA(new_db_player, "typeclass_last_errmsg"))
new_player.basetype_setup() # setup the basic locks and cmdset
# call hook method (may override default permissions)
new_player.at_player_creation()
# custom given arguments potentially overrides the hook
if permissions:
new_player.permissions.add(permissions)
elif not new_player.permissions:
new_player.permissions.add(settings.PERMISSION_PLAYER_DEFAULT)
if locks:
new_player.locks.add(locks)
return new_player
except Exception:
# a failure in creating the player; we try to clean
# up as much as we can
logger.log_trace()
try:
new_player.delete()
except Exception:
pass
try:
del new_player
except Exception:
pass
raise
# saving will trigger the signal that calls the
# at_first_save hook on the typeclass, where the _createdict
# can be used.
new_player.save()
return new_player
# alias
player = create_player

View file

@ -76,12 +76,11 @@ os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
from django.conf import settings
from random import randint
from src.objects.models import ObjectDB
from src.utils.create import handle_dbref
from src.utils.utils import make_iter, all_from_module
from src.utils.utils import make_iter, all_from_module, dbid_to_obj
_CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination")
_handle_dbref = lambda inp: handle_dbref(inp, ObjectDB)
_handle_dbref = lambda inp: dbid_to_obj(inp, ObjectDB)
def _validate_prototype(key, prototype, protparents, visited):