""" Player The Player class is a simple extension of the django User model using the 'profile' system of django. A profile is a model that tack new fields to the User model without actually editing the User model (which would mean hacking into django internals which we want to avoid for future compatability reasons). The profile, which we call 'Player', is accessed with user.get_profile() by the property 'player' defined on ObjectDB objects. Since we can customize it, we will try to abstract as many operations as possible to work on Player rather than on User. We use the Player to store a more mud-friendly style of permission system as well as to allow the admin more flexibility by storing attributes on the Player. Within the game we should normally use the Player manager's methods to create users, since that automatically adds the profile extension. The default Django permission system is geared towards web-use, and is defined on a per-application basis permissions. In django terms, 'src/objects' is one application, 'src/scripts' another, indeed all folders in /src with a model.py inside them is an application. Django permissions thus have the form e.g. 'applicationlabel.permissionstring' and django automatically defines a set of these for editing each application from its automatic admin interface. These are still available should you want them. For most in-game mud-use however, like commands and other things, it does not make sense to tie permissions to the applications in src/ - To the user these should all just be considered part of the game engine. So instead we define our own separate permission system here, borrowing heavily from the django original, but allowing the permission string to look however we want, making them unrelated to the applications. To make the Player model more flexible for your own game, it can also persistently store attributes of its own. This is ideal for extra account info and OOC account configuration variables etc. """ from django.conf import settings from django.db import models from django.contrib.auth.models import User from django.utils.encoding import smart_str from django.contrib.contenttypes.models import ContentType from src.server.sessionhandler import SESSIONS from src.players import manager from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler from src.utils import logger, utils from src.commands.cmdsethandler import CmdSetHandler from src.commands import cmdhandler AT_SEARCH_RESULT = utils.mod_import(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) #------------------------------------------------------------ # # PlayerAttribute # #------------------------------------------------------------ class PlayerAttribute(Attribute): """ PlayerAttributes work the same way as Attributes on game objects, but are intended to store OOC information specific to each user and game (example would be configurations etc). """ db_obj = models.ForeignKey("PlayerDB") class Meta: "Define Django meta options" verbose_name = "Player Attribute" #------------------------------------------------------------ # # Player Nicks # #------------------------------------------------------------ class PlayerNick(TypeNick): """ The default nick types used by Evennia are: inputline (default) - match against all input player - match against player searches obj - match against object searches channel - used to store own names for channels """ db_obj = models.ForeignKey("PlayerDB", verbose_name="player") class Meta: "Define Django meta options" verbose_name = "Nickname for Players" verbose_name_plural = "Nicknames Players" unique_together = ("db_nick", "db_type", "db_obj") class PlayerNickHandler(TypeNickHandler): """ Handles nick access and setting. Accessed through ObjectDB.nicks """ NickClass = PlayerNick #------------------------------------------------------------ # # PlayerDB # #------------------------------------------------------------ class PlayerDB(TypedObject): """ This is a special model using Django's 'profile' functionality and extends the default Django User model. It is defined as such by use of the variable AUTH_PROFILE_MODULE in the settings. One accesses the fields/methods. We try use this model as much as possible rather than User, since we can customize this to our liking. The TypedObject supplies the following (inherited) properties: key - main name name - alias for key typeclass_path - the path to the decorating typeclass typeclass - auto-linked typeclass date_created - time stamp of object creation permissions - perm strings dbref - #id of object db - persistent attribute storage ndb - non-persistent attribute storage The PlayerDB adds the following properties: user - Connected User object. django field, needs to be save():d. obj - game object controlled by player character - alias for obj name - alias for user.username sessions - sessions connected to this player is_superuser - bool if this player is a superuser """ # # PlayerDB Database model setup # # inherited fields (from TypedObject): # db_key, db_typeclass_path, db_date_created, db_permissions # this is the one-to-one link between the customized Player object and # this profile model. It is required by django. user = models.ForeignKey(User, unique=True, help_text="The User object holds django-specific authentication for each Player. A unique User should be created and tied to each Player, the two should never be switched or changed around. The User will be deleted automatically when the Player is.") # the in-game object connected to this player (if any). # Use the property 'obj' to access. db_obj = models.ForeignKey("objects.ObjectDB", null=True, blank=True, verbose_name="character", help_text='In-game object.') # database storage of persistant cmdsets. db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True, help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_DEFAULT.") # Database manager objects = manager.PlayerManager() class Meta: app_label = 'players' verbose_name = 'Player' def __init__(self, *args, **kwargs): "Parent must be initiated first" TypedObject.__init__(self, *args, **kwargs) # handlers self.cmdset = CmdSetHandler(self) self.cmdset.update(init_mode=True) self.nicks = PlayerNickHandler(self) # Wrapper properties to easily set database fields. These are # @property decorators that allows to access these fields using # normal python operations (without having to remember to save() # etc). So e.g. a property 'attr' has a get/set/del decorator # defined that allows the user to do self.attr = value, # value = self.attr and del self.attr respectively (where self # is the object in question). # obj property (wraps db_obj) #@property def obj_get(self): "Getter. Allows for value = self.obj" return self.db_obj #@obj.setter def obj_set(self, value): "Setter. Allows for self.obj = value" from src.typeclasses.typeclass import TypeClass if isinstance(value, TypeClass): value = value.dbobj try: self.db_obj = value self.save() except Exception: logger.log_trace() raise Exception("Cannot assign %s as a player object!" % value) #@obj.deleter def obj_del(self): "Deleter. Allows for del self.obj" self.db_obj = None self.save() obj = property(obj_get, obj_set, obj_del) # whereas the name 'obj' is consistent with the rest of the code, # 'character' is a more intuitive property name, so we # define this too, as an alias to player.obj. #@property def character_get(self): "Getter. Allows for value = self.character" return self.db_obj #@character.setter def character_set(self, value): "Setter. Allows for self.character = value" self.obj = value #@character.deleter def character_del(self): "Deleter. Allows for del self.character" self.db_obj = None self.save() character = property(character_get, character_set, character_del) # cmdset_storage property #@property def cmdset_storage_get(self): "Getter. Allows for value = self.name. Returns a list of cmdset_storage." if self.db_cmdset_storage: return [path.strip() for path in self.db_cmdset_storage.split(',')] return [] #@cmdset_storage.setter def cmdset_storage_set(self, value): "Setter. Allows for self.name = value. Stores as a comma-separated string." if utils.is_iter(value): value = ",".join([str(val).strip() for val in value]) self.db_cmdset_storage = value self.save() #@cmdset_storage.deleter def cmdset_storage_del(self): "Deleter. Allows for del self.name" self.db_cmdset_storage = "" self.save() cmdset_storage = property(cmdset_storage_get, cmdset_storage_set, cmdset_storage_del) class Meta: "Define Django meta options" verbose_name = "Player" verbose_name_plural = "Players" # # PlayerDB main class properties and methods # def __str__(self): return smart_str("%s(player %i)" % (self.name, self.id)) def __unicode__(self): return u"%s(player#%i)" % (self.name, self.id) # this is used by all typedobjects as a fallback try: default_typeclass_path = settings.BASE_PLAYER_TYPECLASS except Exception: default_typeclass_path = "src.players.player.Player" # this is required to properly handle attributes and typeclass loading #attribute_model_path = "src.players.models" #attribute_model_name = "PlayerAttribute" typeclass_paths = settings.PLAYER_TYPECLASS_PATHS attribute_class = PlayerAttribute # name property (wraps self.user.username) #@property def name_get(self): "Getter. Allows for value = self.name" return self.user.username #@name.setter def name_set(self, value): "Setter. Allows for player.name = newname" self.user.username = value self.user.save() # this might be stopped by Django? #@name.deleter def name_del(self): "Deleter. Allows for del self.name" raise Exception("Player name cannot be deleted!") name = property(name_get, name_set, name_del) key = property(name_get, name_set, name_del) # sessions property #@property def sessions_get(self): "Getter. Retrieve sessions related to this player/user" return SESSIONS.sessions_from_player(self) #@sessions.setter def sessions_set(self, value): "Setter. Protects the sessions property from adding things" raise Exception("Cannot set sessions manually!") #@sessions.deleter def sessions_del(self): "Deleter. Protects the sessions property from deletion" raise Exception("Cannot delete sessions manually!") sessions = property(sessions_get, sessions_set, sessions_del) #@property def is_superuser_get(self): "Superusers have all permissions." return self.user.is_superuser is_superuser = property(is_superuser_get) # # PlayerDB class access methods # def msg(self, outgoing_string, from_obj=None, data=None): """ Evennia -> User This is the main route for sending data back to the user from the server. """ if from_obj: try: from_obj.at_msg_send(outgoing_string, to_obj=self, data=data) except Exception: pass if (object.__getattribute__(self, "character") and not self.character.at_msg_receive(outgoing_string, from_obj=from_obj, data=data)): # the at_msg_receive() hook may block receiving of certain messages return outgoing_string = utils.to_str(outgoing_string, force_string=True) for session in object.__getattribute__(self, 'sessions'): session.msg(outgoing_string, data) def swap_character(self, new_character, delete_old_character=False): """ Swaps character, if possible """ return self.__class__.objects.swap_character(self, new_character, delete_old_character=delete_old_character) # # Execution/action methods # def execute_cmd(self, raw_string): """ Do something as this playe. This command transparently lets its typeclass execute the command. raw_string - raw command input coming from the command line. """ # nick replacement - we require full-word matching. raw_string = utils.to_unicode(raw_string) raw_list = raw_string.split(None) raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]] for nick in PlayerNick.objects.filter(db_obj=self, db_type__in=("inputline","channel")): if nick.db_nick in raw_list: raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1) break cmdhandler.cmdhandler(self.typeclass, raw_string) def search(self, ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, player=False): """ A shell method mimicking the ObjectDB equivalent, for easy inclusion from commands regardless of if the command is run by a Player or an Object. """ if self.character: # run the normal search return self.character.search(ostring, global_search=global_search, attribute_name=attribute_name, use_nicks=use_nicks, location=location, ignore_errors=ignore_errors, player=player) if player: # seach for players matches = self.__class__.objects.player_search(ostring) else: # more limited player-only search. Still returns an Object. ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class() matches = ObjectDB.objects.object_search(ostring, caller=self, global_search=global_search) # deal with results matches = AT_SEARCH_RESULT(self, ostring, matches, global_search=global_search) return matches