Run Migrate. Implemented a full separation between Player and Character - Players (OOC entities) can now also hold cmdsets and execute commands. This means that "disconnecting" from a Character becomes possible, putting the Player in an "OOC" state outside the game. This overall makes the game much more stable since there used to be issues if the character was destroyed. Having an OOC set also avoids the previous problem of @puppeting into an object that didn't have any cmdset of its own - you couldn't get back out! A new default OOC-Cmdset handles commands available to a player while OOC. Commands in this set are applied with a low priority, allowing "IC" mode to give precedence if desired.

This change meant several changes to the lock and permission functionality, since it becomes important if permissions are assigned on the Player or on their Character (lock functions pperm() and pid() etc check on Player rather than Character). This has the boon of allowing Admins to switch and play/test the game as a "Low access" character as they like.

Plenty of bug fixes and adjustments. Migrations should make sure to move over all data properly.
This commit is contained in:
Griatch 2011-04-23 11:54:08 +00:00
parent ce2a8e9ffe
commit 28fe2ad3f4
37 changed files with 1622 additions and 555 deletions

View file

@ -146,17 +146,13 @@ class PlayerManager(TypedObjectManager):
ostring = a string or database id.
"""
players = []
try:
# try dbref match
dbref = int(ostring.strip('#'))
players = self.filter(id=dbref)
except Exception:
pass
if not players:
players = self.filter(user__username=ostring)
return players
ostring = ostring.lstrip("*")
dbref = self.dbref(ostring)
if dbref:
matches = self.filter(id=dbref)
if matches:
return matches
return self.filter(user__username__iexact=ostring)
def swap_character(self, player, new_character, delete_old_character=False):
"""

View file

@ -0,0 +1,95 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'PlayerDB.db_cmdset_storage'
db.add_column('players_playerdb', 'db_cmdset_storage', self.gf('django.db.models.fields.TextField')(null=True), keep_default=False)
def backwards(self, orm):
# Deleting field 'PlayerDB.db_cmdset_storage'
db.delete_column('players_playerdb', 'db_cmdset_storage')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'objects.objectdb': {
'Meta': {'object_name': 'ObjectDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destinations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_home': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'homes_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_location': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_player': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']", 'null': 'True', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerattribute': {
'Meta': {'object_name': 'PlayerAttribute'},
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']"}),
'db_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerdb': {
'Meta': {'object_name': 'PlayerDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']", 'null': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
}
}
complete_apps = ['players']

View file

@ -0,0 +1,116 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'PlayerNick'
db.create_table('players_playernick', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('db_nick', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('db_real', self.gf('django.db.models.fields.TextField')()),
('db_type', self.gf('django.db.models.fields.CharField')(default='inputline', max_length=16, null=True, blank=True)),
('db_obj', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['players.PlayerDB'])),
))
db.send_create_signal('players', ['PlayerNick'])
# Adding unique constraint on 'PlayerNick', fields ['db_nick', 'db_type', 'db_obj']
db.create_unique('players_playernick', ['db_nick', 'db_type', 'db_obj_id'])
def backwards(self, orm):
# Removing unique constraint on 'PlayerNick', fields ['db_nick', 'db_type', 'db_obj']
db.delete_unique('players_playernick', ['db_nick', 'db_type', 'db_obj_id'])
# Deleting model 'PlayerNick'
db.delete_table('players_playernick')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'objects.objectdb': {
'Meta': {'object_name': 'ObjectDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destinations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_home': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'homes_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_location': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locations_set'", 'null': 'True', 'to': "orm['objects.ObjectDB']"}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_player': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']", 'null': 'True', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerattribute': {
'Meta': {'object_name': 'PlayerAttribute'},
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']"}),
'db_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'players.playerdb': {
'Meta': {'object_name': 'PlayerDB'},
'db_cmdset_storage': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'db_date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'db_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'db_lock_storage': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['objects.ObjectDB']", 'null': 'True'}),
'db_permissions': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
'db_typeclass_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'players.playernick': {
'Meta': {'unique_together': "(('db_nick', 'db_type', 'db_obj'),)", 'object_name': 'PlayerNick'},
'db_nick': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'db_obj': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['players.PlayerDB']"}),
'db_real': ('django.db.models.fields.TextField', [], {}),
'db_type': ('django.db.models.fields.CharField', [], {'default': "'inputline'", 'max_length': '16', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
}
}
complete_apps = ['players']

View file

@ -44,10 +44,16 @@ 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
from src.utils import logger
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))
#------------------------------------------------------------
#
@ -68,6 +74,36 @@ class PlayerAttribute(Attribute):
verbose_name = "Player Attribute"
verbose_name_plural = "Player Attributes"
#------------------------------------------------------------
#
# 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")
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
@ -116,6 +152,9 @@ class PlayerDB(TypedObject):
# the in-game object connected to this player (if any).
# Use the property 'obj' to access.
db_obj = models.ForeignKey("objects.ObjectDB", null=True)
# database storage of persistant cmdsets.
db_cmdset_storage = models.TextField(null=True)
# Database manager
objects = manager.PlayerManager()
@ -123,6 +162,14 @@ class PlayerDB(TypedObject):
class Meta:
app_label = 'players'
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()
@ -172,6 +219,26 @@ class PlayerDB(TypedObject):
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"
@ -245,15 +312,23 @@ class PlayerDB(TypedObject):
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"):
if self.character.at_msg_receive(outgoing_string, from_obj=from_obj, data=data):
for session in object.__getattribute__(self, 'sessions'):
session.msg(outgoing_string, data)
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):
"""
@ -261,3 +336,49 @@ class PlayerDB(TypedObject):
"""
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(self), 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(self, ostring, global_search=global_search)
# deal with results
matches = AT_SEARCH_RESULT(self, ostring, matches, global_search=global_search)
return matches

View file

@ -12,19 +12,20 @@ instead for most things).
"""
from src.typeclasses.typeclass import TypeClass
from settings import CMDSET_OOC
class Player(TypeClass):
"""
Base typeclass for all Players.
"""
def at_player_creation(self):
def basetype_setup(self):
"""
This sets up the basic properties for a player.
Overload this with at_player_creation rather than
changing this method.
"""
This is called once, the very first time
the player is created (i.e. first time they
register with the game). It's a good place
to store attributes all players should have,
like configuration values etc.
"""
# the text encoding to use.
self.db.encoding = "utf-8"
@ -35,6 +36,21 @@ class Player(TypeClass):
self.locks.add("boot:perm(Wizards)")
self.locks.add("msg:all()")
# The ooc player cmdset
self.cmdset.add_default(CMDSET_OOC, permanent=True)
self.cmdset.outside_access = False
def at_player_creation(self):
"""
This is called once, the very first time
the player is created (i.e. first time they
register with the game). It's a good place
to store attributes all players should have,
like configuration values etc.
"""
pass
# Note that the hooks below also exist
# in the character object's typeclass. You
# can often ignore these and rely on the