""" This module gathers all the essential database-creation functions for the game engine's various object types. Only objects created 'stand-alone' are in here, e.g. object Attributes are always created directly through their respective objects. Each creation_* function also has an alias named for the entity being created, such as create_object() and object(). This is for consistency with the utils.search module and allows you to do the shorter "create.object()". The respective object managers hold more methods for manipulating and searching objects already existing in the database. Models covered: Objects Scripts Help Message Channel Players """ from django.conf import settings from django.db import IntegrityError from src.utils.idmapper.models import SharedMemoryModel from src.utils import utils, logger from src.utils.utils import make_iter, class_from_module, dbid_to_obj # delayed imports _User = None _ObjectDB = None _Object = None _Script = None _ScriptDB = None _HelpEntry = None _Msg = None _Player = None _PlayerDB = None _to_object = None _ChannelDB = None _channelhandler = None # limit symbol import from API __all__ = ("create_object", "create_script", "create_help_entry", "create_message", "create_channel", "create_player") _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 # def create_object(typeclass=None, key=None, location=None, home=None, permissions=None, locks=None, aliases=None, destination=None, report_to=None, nohome=False): """ Create a new in-game object. keywords: typeclass - class or python path to a typeclass key - name of the new object. If not set, a name of #dbref will be set. home - obj or #dbref to use as the object's home location permissions - a comma-separated string of permissions locks - one or more lockstrings, separated by semicolons aliases - a list of alternative keys destination - obj or #dbref to use as an Exit's target nohome - this allows the creation of objects without a default home location; only used when creating the default location itself or during unittests """ typeclass = typeclass if typeclass else settings.BASE_OBJECT_TYPECLASS if isinstance(typeclass, basestring): # a path is given. Load the actual typeclass typeclass = class_from_module(typeclass, settings.OBJECT_TYPECLASS_PATHS) # Setup input for the create command. We use ObjectDB as baseclass here # to give us maximum freedom (the typeclasses will load # correctly when each object is recovered). location = dbid_to_obj(location, _ObjectDB) destination = dbid_to_obj(destination, _ObjectDB) home = dbid_to_obj(home, _ObjectDB) if not home: try: home = dbid_to_obj(settings.DEFAULT_HOME, _ObjectDB) if not nohome else None except _ObjectDB.DoesNotExist: raise _ObjectDB.DoesNotExist("settings.DEFAULT_HOME (= '%s') does not exist, or the setting is malformed." % settings.DEFAULT_HOME) # create new instance new_object = typeclass(db_key=key, db_location=location, db_destination=destination, db_home=home, db_typeclass_path=typeclass.path) # store the call signature for the signal new_object._createdict = {"key":key, "location":location, "destination":destination, "home":home, "typeclass":typeclass.path, "permissions":permissions, "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 # used. new_object.save() return new_object #alias for create_object object = create_object # # Script creation # def create_script(typeclass, key=None, obj=None, player=None, locks=None, interval=None, start_delay=None, repeats=None, persistent=None, autostart=True, report_to=None): """ Create a new script. All scripts are a combination of a database object that communicates with the database, and an typeclass that 'decorates' the database object into being different types of scripts. It's behaviour is similar to the game objects except scripts has a time component and are more limited in scope. Argument 'typeclass' can be either an actual typeclass object or a python path to such an object. Only set key here if you want a unique name for this particular script (set it in config to give same key to all scripts of the same type). Set obj to tie this script to a particular object. See src.scripts.manager for methods to manipulate existing scripts in the database. report_to is an obtional object to receive error messages. If report_to is not set, an Exception with the 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 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 # create new database script new_db_script = _ScriptDB() # 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() return new_script #alias script = create_script # # Help entry creation # def create_help_entry(key, entrytext, category="General", locks=None): """ Create a static help entry in the help database. Note that Command help entries are dynamic and directly taken from the __doc__ entries of the command. The database-stored help entries are intended for more general help on the game, more extensive info, in-game setting information and so on. """ global _HelpEntry if not _HelpEntry: from src.help.models import HelpEntry as _HelpEntry try: new_help = _HelpEntry() new_help.key = key new_help.entrytext = entrytext new_help.help_category = category if locks: new_help.locks.add(locks) new_help.save() return new_help except IntegrityError: string = "Could not add help entry: key '%s' already exists." % key logger.log_errmsg(string) return None except Exception: logger.log_trace() return None # alias help_entry = create_help_entry # # Comm system methods # def create_message(senderobj, message, channels=None, receivers=None, locks=None, header=None): """ Create a new communication message. Msgs are used for all player-to-player communication, both between individual players and over channels. senderobj - the player sending the message. This must be the actual object. message - text with the message. Eventual headers, titles etc should all be included in this text string. Formatting will be retained. channels - a channel or a list of channels to send to. The channels may be actual channel objects or their unique key strings. receivers - a player to send to, or a list of them. May be Player objects or playernames. locks - lock definition string header - mime-type or other optional information for the message The Comm system is created very open-ended, so it's fully possible to let a message both go to several channels and to several receivers at the same time, it's up to the command definitions to limit this as desired. """ global _Msg if not _Msg: from src.comms.models import Msg as _Msg if not message: # we don't allow empty messages. return new_message = _Msg(db_message=message) new_message.save() for sender in make_iter(senderobj): new_message.senders = sender new_message.header = header for channel in make_iter(channels): new_message.channels = channel for receiver in make_iter(receivers): new_message.receivers = receiver if locks: new_message.locks.add(locks) new_message.save() return new_message message = create_message def create_channel(key, aliases=None, desc=None, locks=None, keep_log=True, typeclass=None): """ Create A communication Channel. A Channel serves as a central hub for distributing Msgs to groups of people without specifying the receivers explicitly. Instead players may 'connect' to the channel and follow the flow of messages. By default the channel allows access to all old messages, but this can be turned off with the keep_log switch. key - this must be unique. aliases - list of alternative (likely shorter) keynames. locks - lock string definitions """ global _ChannelDB, _channelhandler if not _ChannelDB: from src.comms.models import ChannelDB as _ChannelDB if not _channelhandler: from src.comms import channelhandler as _channelhandler if not typeclass: typeclass = settings.BASE_CHANNEL_TYPECLASS try: new_channel = _ChannelDB(typeclass=typeclass, db_key=key) new_channel.save() new_channel = new_channel.typeclass if aliases: if not utils.is_iter(aliases): aliases = [aliases] new_channel.aliases.add(aliases) new_channel.save() new_channel.db.desc = desc new_channel.db.keep_log = keep_log except IntegrityError: string = "Could not add channel: key '%s' already exists." % key logger.log_errmsg(string) return None if locks: new_channel.locks.add(locks) new_channel.save() _channelhandler.CHANNELHANDLER.add_channel(new_channel) new_channel.at_channel_create() return new_channel channel = create_channel # # Player creation methods # def create_player(key, email, password, typeclass=None, is_superuser=False, locks=None, permissions=None, report_to=None): """ This creates a new player. key - the player's name. This should be unique. email - email on valid addr@addr.domain form. password - password in cleartext is_superuser - wether or not this player is to be a superuser locks - lockstring permission - list of permissions report_to - an object with a msg() method to report errors to. If not given, errors will be logged. Will return the Player-typeclass or None/raise Exception if the Typeclass given failed to load. Concerning is_superuser: Usually only the server admin should need to be superuser, all other access levels can be handled with more fine-grained permissions or groups. A superuser bypasses all lock checking 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 if not email: email = "dummy@dummy.com" if _PlayerDB.objects.filter(username__iexact=key): 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) try: # 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 # alias player = create_player