Trunk: Merged the Devel-branch (branches/griatch) into /trunk. This constitutes a major refactoring of Evennia. Development will now continue in trunk. See the wiki and the past posts to the mailing list for info. /Griatch

This commit is contained in:
Griatch 2010-08-29 18:46:58 +00:00
parent df29defbcd
commit f83c2bddf8
222 changed files with 22304 additions and 14371 deletions

0
src/objects/__init__.py Executable file → Normal file
View file

View file

@ -1,14 +1,29 @@
from src.objects.models import Attribute, Object
#
# This sets up how models are displayed
# in the web admin interface.
#
from src.objects.models import ObjAttribute, ObjectDB
from django.contrib import admin
class AttributeAdmin(admin.ModelAdmin):
list_display = ('attr_object', 'attr_name', 'attr_value',)
search_fields = ['attr_name']
admin.site.register(Attribute, AttributeAdmin)
class ObjAttributeAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_value', 'db_mode', 'db_obj', 'db_permissions')
list_display_links = ("id", 'db_key')
ordering = ["db_obj", 'db_key']
readonly_fields = ['db_permissions']
search_fields = ['id', 'db_key', 'db_obj']
save_as = True
save_on_top = True
list_select_related = True
admin.site.register(ObjAttribute, ObjAttributeAdmin)
class ObjectAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'type', 'date_created')
list_filter = ('type',)
search_fields = ['name']
class ObjectDBAdmin(admin.ModelAdmin):
list_display = ('id', 'db_key', 'db_typeclass_path', 'db_location', 'db_player')
list_display_links = ('id', 'db_key')
ordering = ['id', 'db_typeclass_path']
readonly_fields = ['db_permissions']
search_fields = ['^db_key', 'db_typeclass_path']
save_as = True
save_on_top = True
admin.site.register(Object, ObjectAdmin)
list_select_related = True
admin.site.register(ObjectDB, ObjectDBAdmin)

View file

@ -1,11 +0,0 @@
"""
Exceptions for the object application.
"""
from src.exceptions_generic import GenericException
class ObjectNotExist(GenericException):
"""
Raised when an object is queried for but does not exist.
"""
def __str__(self):
return repr("No such object: %s" % self.value)

View file

@ -0,0 +1,88 @@
"""
This handler creates cmdsets on the fly, by searching
an object's location for valid exit objects.
"""
from src.commands import cmdset, command
from src.permissions.permissions import has_perm
class ExitCommand(command.Command):
"Simple identifier command"
is_exit = True
destination = None
obj = None
def func(self):
"Default exit traverse if no syscommand is defined."
if has_perm(self.caller, self.obj, 'traverse'):
self.caller.move_to(self.destination)
else:
self.caller.msg("You cannot enter.")
class ExitHandler(object):
"""
The exithandler auto-creates 'commands' to represent exits in the
room. It is called by cmdhandler when building its index of all
viable commands. This allows for exits to be processed along with
all other inputs the player gives to the game. The handler tries
to intelligently cache exit objects to cut down on processing.
"""
def __init__(self):
"Setup cache storage"
self.cached_exit_cmds = {}
def reset(self, exitcmd=None):
"""
Reset cache storage. If obj is given, only remove
that object from cache.
"""
if exitcmd:
# delete only a certain object from cache
try:
del self.cached_exit_cmds[exitcmd.id]
except KeyError:
pass
return
# reset entire cache
self.cached_exit_cmds = {}
def get_cmdset(self, srcobj):
"""
Search srcobjs location for valid exits, and
return objects as stored in command set
"""
# create a quick "throw away" cmdset
exit_cmdset = cmdset.CmdSet(None)
exit_cmdset.key = '_exitset'
exit_cmdset.priority = 9
try:
location = srcobj.location
except Exception:
location = None
if not location:
# there can be no exits if there's no location
return exit_cmdset
# use exits to create searchable "commands" for the cmdhandler
for exi in (exi for exi in location.contents
if exi.has_attribute('_destination')):
if exi.id in self.cached_exit_cmds:
# retrieve from cache
exit_cmdset.add(self.cached_exit_cmds[exi.id])
else:
# not in cache, create a new exit command
cmd = ExitCommand()
cmd.key = exi.name.strip().lower()
cmd.obj = exi
if exi.aliases:
cmd.aliases = exi.aliases
cmd.destination = exi.attr('_destination')
exit_cmdset.add(cmd)
self.cached_exit_cmds[exi.id] = cmd
return exit_cmdset
# The actual handler - call this to get exits
EXITHANDLER = ExitHandler()

388
src/objects/manager.py Normal file
View file

@ -0,0 +1,388 @@
"""
Custom manager for Objects.
"""
from django.conf import settings
from django.contrib.auth.models import User
from src.typeclasses.managers import TypedObjectManager
from src.typeclasses.managers import returns_typeclass_list
from src.utils import create
# Try to use a custom way to parse id-tagged multimatches.
try:
IDPARSER = __import__(
settings.ALTERNATE_OBJECT_SEARCH_MULTIMATCH_PARSER).object_multimatch_parser
except Exception:
from src.objects.object_search_funcs import object_multimatch_parser as IDPARSER
#
# Helper function for the ObjectManger's search methods
#
def match_list(searchlist, ostring, exact_match=True,
attribute_name=None):
"""
Helper function.
does name/attribute matching through a list of objects.
"""
ostring = ostring.lower()
if attribute_name:
#search an arbitrary attribute name for a value match.
if exact_match:
return [prospect for prospect in searchlist
if (hasattr(prospect, attribute_name) and
ostring == str(getattr(prospect, attribute_name)).lower()) \
or (ostring == str(prospect.get_attribute(attribute_name)).lower())]
else:
return [prospect for prospect in searchlist
if (hasattr(prospect, attribute_name) and
ostring in str(getattr(prospect, attribute_name)).lower()) \
or (ostring in (str(p).lower() for p in prospect.get_attribute(attribute_name)))]
else:
#search the default "key" attribute
if exact_match:
return [prospect for prospect in searchlist
if ostring == str(prospect.key).lower()]
else:
return [prospect for prospect in searchlist
if ostring in str(prospect.key).lower()]
class ObjectManager(TypedObjectManager):
"""
This is the main ObjectManager for all in-game objects. It
implements search functions specialized for objects of this
type, such as searches based on user, contents or location.
See src.dbobjects.TypedObjectManager for more general
search methods.
"""
#
# ObjectManager Get methods
#
@returns_typeclass_list
def get_object_with_user(self, user):
"""
Matches objects with obj.player.user matching the argument.
Both an user object and a user id may be supplied.
"""
try:
uid = int(user)
except TypeError:
uid = user.id
return self.filter(db_player__user__id=uid)
# This returns typeclass since get_object_with_user and get_dbref does.
def player_name_search(self, search_string):
"""
Search for an object based on its player's name or dbref.
This search
is sometimes initiated by appending a * to the beginning of
the search criterion (e.g. in local_and_global_search).
search_string: (string) The name or dbref to search for.
"""
search_string = str(search_string).lstrip('*')
dbref = self.dbref(search_string)
if dbref:
# this is a valid dbref. Try to match it.
dbref_match = self.dbref_search(dbref)
if dbref_match:
return dbref_match
# not a dbref. Search by name.
player_matches = User.objects.filter(username__iexact=search_string)
if player_matches:
uid = player_matches[0].id
return self.get_object_with_user(uid)
return None
@returns_typeclass_list
def get_objs_with_attr_match(self, attribute_name, attribute_value):
"""
Returns all objects having the valid
attrname set to the given value. Note that no conversion is made
to attribute_value, and so it can accept also non-strings.
"""
return [prospect for prospect in self.all()
if attribute_value
and attribute_value == prospect.get_attribute(attribute_name)]
@returns_typeclass_list
def get_objs_with_attr(self, attribute_name):
"""
Returns all objects having the given attribute_name defined at all.
"""
return [prospect for prospect in self.all()
if prospect.get_attribute(attribute_name)]
@returns_typeclass_list
def get_contents(self, location, excludeobj=None):
"""
Get all objects that has a location
set to this one.
"""
oquery = self.filter(db_location__id=location.id)
if excludeobj:
oquery = oquery.exclude(db_key=excludeobj)
return oquery
@returns_typeclass_list
def alias_list_search(self, ostring, objlist):
"""
Search a list of objects by trying to match their aliases.
"""
matches = []
for obj in (obj for obj in objlist
if hasattr(obj, 'aliases') and
ostring in obj.aliases):
matches.append(obj)
return matches
@returns_typeclass_list
def separable_search(self, ostring, searchlist=None,
attribute_name=None, exact_match=False):
"""
Searches for a object hit for ostring.
This version handles search criteria of the type N-keyword, this is used
to differentiate several objects of the exact same name, e.g. 1-box, 2-box etc.
ostring: (string) The string to match against.
searchlist: (List of Objects) The objects to perform name comparisons on.
if not given, will search the database normally.
attribute_name: (string) attribute name to search, if None, object key is used.
exact_match: (bool) 'exact' or 'fuzzy' matching.
Note that the fuzzy matching gives precedence to exact matches; so if your
search query matches an object in the list exactly, it will be the only result.
This means that if the list contains [box,box11,box12], the search string 'box'
will only match the first entry since it is exact. The search 'box1' will however
match both box11 and box12 since neither is an exact match.
This method always returns a list, also for a single result.
"""
def run_dbref_search(ostring):
"dbref matching only"
dbref = self.dbref(ostring)
if searchlist:
results = [prospect for prospect in searchlist
if prospect.id == dbref]
else:
results = self.filter(id=dbref)
return results
def run_full_search(ostring, searchlist, exact_match=False):
"full matching"
if searchlist:
results = match_list(searchlist, ostring,
exact_match, attribute_name)
elif attribute_name:
results = match_list(self.all(), ostring,
exact_match, attribute_name)
elif exact_match:
results = self.filter(db_key__iexact=ostring)
else:
results = self.filter(db_key__icontains=ostring)
return results
# Easiest case - dbref matching (always exact)
if self.dbref(ostring):
results = run_dbref_search(ostring)
if results:
return results
# Full search - this may return multiple matches.
results = run_full_search(ostring, searchlist, exact_match)
# Deal with results of full search
match_number = None
if not results:
# if we have no match, check if we are dealing
# with a "N-keyword" query, if so, strip it out.
match_number, ostring = IDPARSER(ostring)
if match_number != None and ostring:
# Run the search again, without the match number
results = run_full_search(ostring, searchlist, exact_match)
elif not exact_match:
# we have results, but are using fuzzy matching; run
# second sweep in results to catch eventual exact matches
# (these are given precedence, so a search for 'ball' in
# ['ball', 'ball2'] will correctly return the first ball
# only).
exact_results = run_full_search(ostring, results, True)
if exact_results:
results = exact_results
if len(results) > 1 and match_number != None:
# We have multiple matches, but a N-type match number
# is available to separate them.
try:
results = [results[match_number]]
except IndexError:
pass
# this is always a list.
return results
@returns_typeclass_list
def object_search(self, character, ostring,
global_search=False,
attribute_name=None):
"""
Search as an object and return results.
character: (Object) The object performing the search.
ostring: (string) The string to compare names against.
Can be a dbref. If name is appended by *, a player is searched for.
global_search: Search all objects, not just the current location/inventory
attribute_name: (string) Which attribute to search in each object.
If None, the default 'name' attribute is used.
"""
ostring = str(ostring).strip()
if not ostring or not character:
return None
dbref = self.dbref(ostring)
if dbref:
# this is a valid dbref. If it matches, we return directly.
dbref_match = self.dbref_search(dbref)
if dbref_match:
return [dbref_match]
location = character.location
# If the search string is one of the following, return immediately with
# the appropriate result.
if location and ostring == 'here':
return [location]
if character and ostring in ['me', 'self']:
return [character]
if character and ostring in ['*me', '*self']:
return [character.player]
if ostring.startswith("*"):
# Player search - search player base
player_string = ostring.lstrip("*")
player_match = self.player_name_search(player_string)
if player_match is not None:
return [player_match]
if global_search or not location:
# search all objects
return self.separable_search(ostring, None,
attribute_name)
# None of the above cases yielded a return, so we fall through to
# location/contents searches.
matches = []
local_objs = []
local_objs.extend(character.contents)
local_objs.extend(location.contents)
local_objs.append(location) #easy to forget!
if local_objs:
# normal key/attribute search (typedobject_search is
# found in class parent)
matches = self.separable_search(ostring, local_objs,
attribute_name, exact_match=False)
if not matches:
# no match, try an alias search
matches = self.alias_list_search(ostring, local_objs)
return matches
#
# ObjectManager Copy method
#
def copy_object(self, original_object, new_name=None,
new_location=None, new_home=None, aliases=None):
"""
Create and return a new object as a copy of the source object. All will
be identical to the original except for the dbref and the 'user' field
which will be set to None.
original_object (obj) - the object to make a copy from
new_name (str) - name the copy differently from the original.
new_location (obj) - if None, we create the new object in the same place as the old one.
"""
# get all the object's stats
name = original_object.key
if new_name:
name = new_name
typeclass_path = original_object.typeclass_path
# create new object
from src import create
new_object = create.create_object(name, typeclass_path, new_location,
new_home, user=None, aliases=None)
if not new_object:
return None
for attr in original_object.attr():
# copy over all attributes from old to new.
new_object.attr(attr.attr_name, attr.value)
return new_object
#
# ObjectManager User control
#
def user_swap_object(self, uid, new_obj_id, delete_old_obj=False):
"""
This moves the user from one database object to another.
The new object must already exist.
delete_old_obj (bool) - Delete the user's old dbobject.
This is different from ObjectDB.swap_type() since it actually
swaps the database object the user is connected to, rather
than change any typeclass on the same dbobject. This means
that the old object (typeclass and all) can remain unchanged
in-game except it is now not tied to any user.
Note that the new object will be unchanged, the only
difference is that its 'user' property is set to the
user. No other initializations are done here, such as
setting the default cmdset - this has to be done
separately when calling this method.
This method raises Exceptions instead of logging feedback
since this is a method which might be very useful to embed in
your own game implementation.
Also note that this method don't check any permissions beyond
making sure no other user is connected to the object before
swapping.
"""
# get the objects.
try:
user = User.get(uid)
new_obj = self.get(new_obj_id)
except:
raise Exception("OBJ_FIND_ERROR")
# check so the new object is not already controlled.
if new_obj.user:
if new_obj.user == user:
raise Exception("SELF_CONTROL_ERROR")
else:
raise Exception("CONTROL_ERROR")
# set user to new object.
new_obj.user = user
new_obj.save()
# get old object, sets its user to None and/or delete it
for old_obj in self.get_object_with_user(uid):
if delete_old_obj:
old_obj.delete()
else:
old_obj.user = None
old_obj.save()

View file

@ -1,18 +0,0 @@
"""
Custom manager for Attribute objects.
"""
from django.db import models
from src import defines_global
class AttributeManager(models.Manager):
def is_modifiable_attrib(self, attribname):
"""
Check to see if a particular attribute is modifiable.
attribname: (string) An attribute name to check.
"""
if attribname.upper() not in defines_global.NOSET_ATTRIBS:
return True
else:
return False

View file

@ -1,598 +0,0 @@
"""
Custom manager for Object objects.
"""
from datetime import datetime, timedelta
from django.db import models
from django.db import connection
from django.contrib.auth.models import User
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
from src.config.models import ConfigValue
from src.objects.exceptions import ObjectNotExist
from src.objects.util import object as util_object
from src import defines_global
from src import logger
class ObjectManager(models.Manager):
#
# ObjectManager Get methods
#
def num_total_players(self):
"""
Returns the total number of registered players.
"""
return User.objects.count()
def get_connected_players(self):
"""
Returns the a QuerySet containing the currently connected players.
"""
return self.filter(nosave_flags__contains="CONNECTED")
def get_recently_created_users(self, days=7):
"""
Returns a QuerySet containing the player User accounts that have been
connected within the last <days> days.
"""
end_date = datetime.now()
tdelta = timedelta(days)
start_date = end_date - tdelta
return User.objects.filter(date_joined__range=(start_date, end_date))
def get_recently_connected_users(self, days=7):
"""
Returns a QuerySet containing the player User accounts that have been
connected within the last <days> days.
"""
end_date = datetime.now()
tdelta = timedelta(days)
start_date = end_date - tdelta
return User.objects.filter(last_login__range=(start_date, end_date)).order_by('-last_login')
def get_user_from_email(self, uemail):
"""
Returns a player's User object when given an email address.
"""
return User.objects.filter(email__iexact=uemail)
def get_object_from_dbref(self, dbref):
"""
Returns an object when given a dbref.
"""
if type(dbref) == type(str()):
if len(dbref)>1 and dbref[0]=="#":
dbref = dbref[1:]
dbref = "%s" % dbref
try:
return self.get(id=dbref)
except self.model.DoesNotExist:
raise ObjectNotExist(dbref)
def object_totals(self):
"""
Returns a dictionary with database object totals.
"""
dbtotals = {
"objects": self.count(),
"things": self.filter(type=defines_global.OTYPE_THING).count(),
"exits": self.filter(type=defines_global.OTYPE_EXIT).count(),
"rooms": self.filter(type=defines_global.OTYPE_ROOM).count(),
"garbage": self.filter(type=defines_global.OTYPE_GARBAGE).count(),
"players": self.filter(type=defines_global.OTYPE_PLAYER).count(),
}
return dbtotals
def get_nextfree_dbnum(self):
"""
Figure out what our next free database reference number is.
If we need to recycle a GARBAGE object, return the object to recycle
Otherwise, return the first free dbref.
"""
# First we'll see if there's an object of type 6 (GARBAGE) that we
# can recycle.
nextfree = self.filter(type__exact=defines_global.OTYPE_GARBAGE)
if nextfree:
# We've got at least one garbage object to recycle.
return nextfree[0]
else:
# No garbage to recycle, find the highest dbnum and increment it
# for our next free.
return int(self.order_by('-id')[0].id + 1)
def is_dbref(self, dbstring, require_pound=True):
"""
Is the input a well-formed dbref number?
"""
return util_object.is_dbref(dbstring, require_pound=require_pound)
#
# ObjectManager Search methods
#
def dbref_search(self, dbref_string, limit_types=False):
"""
Searches for a given dbref.
dbref_number: (string) The dbref to search for. With # sign.
limit_types: (list of int) A list of Object type numbers to filter by.
"""
if not util_object.is_dbref(dbref_string):
return None
dbref_string = dbref_string[1:]
dbref_matches = self.filter(id=dbref_string).exclude(
type=defines_global.OTYPE_GARBAGE)
# Check for limiters
if limit_types is not False:
for limiter in limit_types:
dbref_matches.filter(type=limiter)
try:
return dbref_matches[0]
except IndexError:
return None
def global_object_name_search(self, ostring, exact_match=True, limit_types=[]):
"""
Searches through all objects for a name match.
limit_types is a list of types as defined in defines_global.
"""
if self.is_dbref(ostring):
o_query = self.dbref_search(ostring, limit_types=limit_types)
if o_query:
return [o_query]
return None
# get rough match
o_query = self.filter(name__icontains=ostring)
o_query = o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING])
if not o_query:
# use list-search to catch N-style queries. Note
# that we want to keep the original ostring since
# search_object_namestr does its own N-string treatment
# on this.
dum, test_ostring = self._parse_match_number(ostring)
o_query = self.filter(name__icontains=test_ostring)
o_query = o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING])
match_type = "fuzzy"
if exact_match:
match_type = "exact"
return self.list_search_object_namestr(o_query, ostring,
limit_types=limit_types,
match_type=match_type)
return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING])
def global_object_script_parent_search(self, script_parent):
"""
Searches through all objects returning those which has a certain script parent.
"""
o_query = self.filter(script_parent__exact=script_parent)
return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING])
def local_object_script_parent_search(self, script_parent, location):
o_query = self.filter(script_parent__exact=script_parent)
if o_query:
o_query = o_query.filter(location__iexact=location)
return o_query.exclude(type__in=[defines_global.OTYPE_GARBAGE,
defines_global.OTYPE_GOING])
def list_search_object_namestr(self, searchlist, ostring, dbref_only=False,
limit_types=False, match_type="fuzzy",
attribute_name=None):
"""
Iterates through a list of objects and returns a list of
name matches.
This version handles search criteria of the type N-keyword, this is used
to differentiate several objects of the exact same name, e.g. 1-box, 2-box etc.
searchlist: (List of Objects) The objects to perform name comparisons on.
ostring: (string) The string to match against.
dbref_only: (bool) Only compare dbrefs.
limit_types: (list of int) A list of Object type numbers to filter by.
match_type: (string) 'exact' or 'fuzzy' matching.
attribute_name: (string) attribute name to search, if None, 'name' is used.
Note that the fuzzy matching gives precedence to exact matches; so if your
search query matches an object in the list exactly, it will be the only result.
This means that if the list contains [box,box11,box12], the search string 'box'
will only match the first entry since it is exact. The search 'box1' will however
match both box11 and box12 since neither is an exact match.
Uses two helper functions, _list_search_helper1/2.
"""
if dbref_only:
#search by dbref - these must always be unique.
if limit_types:
return [prospect for prospect in searchlist
if prospect.dbref_match(ostring)
and prospect.type in limit_types]
else:
return [prospect for prospect in searchlist
if prospect.dbref_match(ostring)]
#search by name - this may return multiple matches.
results = self._match_name_attribute(searchlist,ostring,dbref_only,
limit_types, match_type,
attribute_name=attribute_name)
match_number = None
if not results:
#if we have no match, check if we are dealing
#with a "N-keyword" query - if so, strip it and run again.
match_number, ostring = self._parse_match_number(ostring)
if match_number != None and ostring:
results = self._match_name_attribute(searchlist,ostring,dbref_only,
limit_types, match_type,
attribute_name=attribute_name)
if match_type == "fuzzy":
#fuzzy matching; run second sweep to catch exact matches
if attribute_name:
exact_results = [prospect for prospect in results
if ostring == prospect.get_attribute_value(attribute_name)]
else:
exact_results = [prospect for prospect in results
if prospect.name_match(ostring, match_type="exact")]
if exact_results:
results = exact_results
if len(results) > 1 and match_number != None:
#select a particular match using the "keyword-N" markup.
try:
results = [results[match_number]]
except IndexError:
pass
return results
def _match_name_attribute(self, searchlist, ostring, dbref_only,
limit_types, match_type,
attribute_name=None):
"""
Helper function for list_search_object_namestr -
does name/attribute matching through a list of objects.
"""
if attribute_name:
#search an arbitrary attribute name.
if limit_types:
if match_type == "exact":
return [prospect for prospect in searchlist
if prospect.type in limit_types and
ostring == prospect.get_attribute_value(attribute_name)]
else:
return [prospect for prospect in searchlist
if prospect.type in limit_types and
ostring in str(prospect.get_attribute_value(attribute_name))]
else:
if match_type == "exact":
return [prospect for prospect in searchlist
if ostring == str(prospect.get_attribute_value(attribute_name))]
else:
return [prospect for prospect in searchlist
if ostring in str(prospect.get_attribute_value(attribute_name))]
else:
#search the default "name" attribute
if limit_types:
return [prospect for prospect in searchlist
if prospect.type in limit_types and
prospect.name_match(ostring, match_type=match_type)]
else:
return [prospect for prospect in searchlist
if prospect.name_match(ostring, match_type=match_type)]
def _parse_match_number(self, ostring):
"""
Helper function for list_search_object_namestr -
strips eventual N-keyword endings from a search criterion
"""
if not '-' in ostring:
return False, ostring
try:
il = ostring.find('-')
number = int(ostring[:il])-1
return number, ostring[il+1:]
except ValueError:
#not a number; this is not an identifier.
return None, ostring
except IndexError:
return None, ostring
def player_alias_search(self, searcher, ostring):
"""
Search players by alias. Returns a list of objects whose "ALIAS"
attribute exactly (not case-sensitive) matches ostring.
searcher: (Object) The object doing the searching.
ostring: (string) The alias string to search for.
"""
if ostring.lower().strip() == "me":
return searcher
Attribute = ContentType.objects.get(app_label="objects",
model="attribute").model_class()
results = Attribute.objects.select_related().filter(attr_name__exact="ALIAS").filter(attr_value__iexact=ostring)
return [prospect.get_object() for prospect in results if prospect.get_object().is_player()]
def player_name_search(self, search_string):
"""
Combines an alias and global search for a player's name. If there are
no alias matches, do a global search limiting by type PLAYER.
search_string: (string) The name string to search for.
"""
# Handle the case where someone might have started the search_string
# with a *
if search_string.startswith('*') is True:
search_string = search_string[1:]
# Use Q objects to build complex OR query to look at either
# the player name or ALIAS attribute
player_filter = Q(name__iexact=search_string)
alias_filter = Q(attribute__attr_name__exact="ALIAS") & \
Q(attribute__attr_value__iexact=search_string)
player_matches = self.filter(
player_filter | alias_filter).filter(
type=defines_global.OTYPE_PLAYER).distinct()
try:
return player_matches[0]
except IndexError:
return None
def local_and_global_search(self, searcher, ostring, search_contents=True,
search_location=True, dbref_only=False,
limit_types=False, attribute_name=None):
"""
Searches an object's location then globally for a dbref or name match.
searcher: (Object) The object performing the search.
ostring: (string) The string to compare names against.
search_contents: (bool) While true, check the contents of the searcher.
search_location: (bool) While true, check the searcher's surroundings.
dbref_only: (bool) Only compare dbrefs.
limit_types: (list of int) A list of Object type numbers to filter by.
attribute_name: (string) Which attribute to search in each object.
If None, the default 'name' attribute is used.
"""
search_query = str(ostring).strip()
# This is a global dbref search. Not applicable if we're only searching
# searcher's contents/locations, dbref comparisons for location/contents
# searches are handled by list_search_object_namestr() below.
if util_object.is_dbref(ostring):
dbref_match = self.dbref_search(search_query, limit_types)
if dbref_match is not None:
return [dbref_match]
# If the search string is one of the following, return immediately with
# the appropriate result.
if searcher.get_location().dbref_match(ostring) or ostring == 'here':
return [searcher.get_location()]
elif ostring == 'me' and searcher:
return [searcher]
if search_query and search_query[0] == "*":
# Player search- gotta search by name or alias
search_target = search_query[1:]
player_match = self.player_name_search(search_target)
if player_match is not None:
return [player_match]
# Handle our location/contents searches. list_search_object_namestr() does
# name and dbref comparisons against search_query.
local_objs = []
if search_contents:
local_objs.extend(searcher.get_contents())
if search_location:
local_objs.extend(searcher.get_location().get_contents())
return self.list_search_object_namestr(local_objs, search_query,
limit_types=limit_types,
attribute_name=attribute_name)
#
# ObjectManager Create methods
#
def create_object(self, name, otype, location, owner, home=None, script_parent=None):
"""
Create a new object
type: Integer representing the object's type.
name: The name of the new object.
location: Reference to another object for the new object to reside in.
owner: The creator of the object.
home: Reference to another object to home to. If not specified,
set to location.
script_parent: The parent of this object (ignored if otype is OTYPE_PLAYER)
"""
#get_nextfree_dbnum() returns either an integer or an old object to recycle.
next_dbref = self.get_nextfree_dbnum()
if type(next_dbref) == type(int()):
#create new object with a fresh dbref
Object = ContentType.objects.get(app_label="objects",
model="object").model_class()
new_object = Object()
new_object.id = next_dbref
else:
#recycle an old object's id instead
new_object = next_dbref
new_object.purge_object()
new_object.type = otype
new_object.set_name(name)
# Set the script_parent.
# If the script_parent string is not valid, the defaults will be used.
# To see if it worked or not from outside this method, easiest is to use the
# obj.get_script_parent() function to find out what was actually set.
new_object.set_script_parent(script_parent)
# If this is a player, we don't want him owned by anyone.
# The get_owner() function will return that the player owns
# himself.
if otype == defines_global.OTYPE_PLAYER:
new_object.owner = None
new_object.zone = None
else:
if owner == None:
# if owner is None for a non-player object we are probably
# creating an object from a script. In this case we set
# the owner to be the superuser.
owner = self.get_object_from_dbref("#1")
new_object.owner = owner
if new_object.get_owner().get_zone():
new_object.zone = new_object.get_owner().get_zone()
# Set default description, depending on type.
default_desc = None
if otype == defines_global.OTYPE_PLAYER:
default_desc = defines_global.DESC_PLAYER
elif otype == defines_global.OTYPE_ROOM:
default_desc = defines_global.DESC_ROOM
location = None
elif otype == defines_global.OTYPE_EXIT:
default_desc = defines_global.DESC_EXIT
else:
default_desc = defines_global.DESC_THING
new_object.set_attribute("desc", default_desc)
# Run the script parent's creation hook function.
# This is where all customization happens.
new_object.scriptlink.at_object_creation()
# If we have a 'home' key, use that for our home value. Otherwise use
# the location key. All objects must have this
if home:
new_object.home = home
else:
if new_object.is_exit():
new_object.home = None
else:
new_object.home = location
new_object.save()
# Rooms have a NULL location. Move everything else to new location.
if not new_object.is_room():
new_object.move_to(location, quiet=True, force_look=False)
return new_object
def create_user(self, command, uname, email, password):
"""
Handles the creation of new users.
"""
start_room = int(ConfigValue.objects.get_configvalue('player_dbnum_start'))
start_room_obj = self.get_object_from_dbref(start_room)
# The user's entry in the User table must match up to an object
# on the object table. The id's are the same, we need to figure out
# the next free unique ID to use and make sure the two entries are
# the same number.
uid = self.get_nextfree_dbnum()
# If this is an object, we know to recycle it since it's garbage. We'll
# pluck the user ID from it.
if not str(uid).isdigit():
uid = uid.id
logger.log_infomsg('Create_user: Recycling object ID %d.' % uid)
user = User.objects.create_user(uname, email, password)
# It stinks to have to do this but it's the only trivial way now.
user.save()
# We can't use the user model to change the id because of the way keys
# are handled, so we actually need to fall back to raw SQL. Boo hiss.
cursor = connection.cursor()
cursor.execute("UPDATE auth_user SET id=%d WHERE id=%d" % (uid, user.id))
# Update the session to use the newly created User object's ID.
command.session.uid = uid
# Grab the user object again since we've changed it and the old reference
# is no longer valid.
user = User.objects.get(id=uid)
# Create a player object of the same ID in the Objects table.
user_object = self.create_object(uname,
defines_global.OTYPE_PLAYER,
start_room_obj,
None)
# The User and player Object are ready, do anything needed by the
# game to further prepare things.
user_object.scriptlink.at_player_creation()
# Add the user to all of the CommChannel objects that are flagged
# is_joined_by_default.
command.session.add_default_channels()
# Add user to the default permission group, if defined set in preferences.
command.session.add_default_group()
logger.log_infomsg("Registered new user: %s" % user_object.get_name())
# Activate the player's session and set them loose.
command.session.login(user, first_login=True)
#
# ObjectManager Copy method
#
def copy_object(self, original_object, new_name=None, new_location=None, reset=False):
"""
Create and return a new object as a copy of the source object. All will
be identical to the original except for the dbref. Does not allow the
copying of Player objects.
original_object (obj) - the object to make a copy from
new_location (obj) - if None, we create the new object in the same place as the old one.
reset (bool) - copy only the default attributes/flags set by the script_parent, ignoring
any changes to the original after it was originally created.
"""
if not original_object or original_object.is_player():
return
# get all the object's stats
if new_name:
name = new_name
else:
name = original_object.get_name(show_dbref=False,no_ansi=True)
otype = original_object.type
if new_location:
location = new_location
else:
location = original_object.get_location()
owner = original_object.get_owner()
home = original_object.get_home()
script_parent = original_object.get_script_parent()
# create new object
new_object = self.create_object(name, otype, location, owner, home,
script_parent=script_parent)
if not new_object:
return
if not reset:
# we make sure that the objects are identical by manually copying over all attributes and
# flags; this way we also get those that might have changed since the original was created.
all_attribs = original_object.get_all_attributes()
for attr in all_attribs:
new_object.set_attribute(attr.get_name(), attr.get_value())
all_flags = original_object.get_flags() #this is a string
for flag in all_flags.split():
new_object.set_flag(flag)
return new_object

1896
src/objects/models.py Executable file → Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,104 @@
"""
Default functions for formatting and processing object searches.
This is in its own module due to them being possible to
replace from the settings file by use of setting the variables
ALTERNATE_OBJECT_SEARCH_ERROR_HANDLER
ALTERNATE_OBJECT_SEARCH_MULTIMATCH_PARSER
Both the replacing functions must have the same name and same input/output
as the ones in this module.
"""
from src.permissions.permissions import has_perm_string
def handle_search_errors(emit_to_obj, ostring, results, global_search=False):
"""
Takes a search result (a list) and
formats eventual errors.
emit_to_obj - object to receive feedback.
ostring - original search string
results - list of object matches, if any
global_search - if this was a global_search or not
(if it is, there might be an idea of supplying
dbrefs instead of only numbers)
"""
if not results:
emit_to_obj.emit_to("Could not find '%s'." % ostring)
return None
if len(results) > 1:
# we have more than one match. We will display a
# list of the form 1-objname, 2-objname etc.
# check if the emit_to_object may se dbrefs
show_dbref = global_search and \
has_perm_string(emit_to_obj, 'see_dbref')
string = "More than one match for '%s'" % ostring
string += " (please narrow target):"
for num, result in enumerate(results):
invtext = ""
dbreftext = ""
if result.location == emit_to_obj:
invtext = " (carried)"
if show_dbref:
dbreftext = "(#%i)" % result.id
string += "\n %i-%s%s%s" % (num+1, result.name,
dbreftext, invtext)
emit_to_obj.emit_to(string)
return None
else:
return results[0]
def object_multimatch_parser(ostring):
"""
Parse number-identifiers.
Sometimes it can happen that there are several objects in the room
all with exactly the same key/identifier. Showing dbrefs to
separate them is not suitable for all types of games since it's
unique to that object (and e.g. in rp-games the object might not
want to be identified like that). Instead Evennia allows for
dbref-free matching by letting the user number which of the
objects in a multi-match they want.
Ex for use in game session:
> look
You see: ball, ball, ball and ball.
> get ball
There where multiple matches for ball:
1-ball
2-ball
3-ball
4-ball
> get 3-ball
You get the ball.
The actual feedback upon multiple matches has to be
handled by the searching command. The syntax shown above is the
default.
For replacing, the method must be named the same and
take the searchstring as argument and
return a tuple (int, string) where int is the identifier
matching which of the results (in order) should be used to
pick out the right match from the multimatch). Note
that the engine assumes this number to start with 1 (i.e. not
zero as in normal Python).
"""
if not '-' in ostring:
return (None, ostring)
try:
index = ostring.find('-')
number = int(ostring[:index])-1
return (number, ostring[index+1:])
except ValueError:
#not a number; this is not an identifier.
return (None, ostring)
except IndexError:
return (None, ostring)

376
src/objects/objects.py Normal file
View file

@ -0,0 +1,376 @@
"""
This is the basis of the typeclass system.
The idea is have the object as a normal class with the
database-connection tied to itself through a property.
The instances of all the different object types are all tied to their
own database object stored in the 'dbobj' property. All attribute
get/set operations are channeled transparently to the database object
as desired. You should normally never have to worry about the database
abstraction, just do everything on the TypeClass object.
That an object is controlled by a player/user is just defined by its
'user' property being set. This means a user may switch which object
they control by simply linking to a new object's user property.
"""
from src.typeclasses.typeclass import TypeClass
from src.commands.cmdsethandler import CmdSetHandler
from src.scripts.scripthandler import ScriptHandler
#from src.permissions.permissions import has_perm
from src.objects.exithandler import EXITHANDLER
#
# Base class to inherit from.
#
class Object(TypeClass):
"""
This is the base class for all in-game objects.
Inherit from this to create different types of
objects in the game.
"""
def __init__(self, dbobj):
"""
Set up the Object-specific handlers. Note that we must
be careful to run the parent's init function too
or typeclasses won't work!
"""
# initialize typeclass system. This sets up self.dbobj.
super(Object, self).__init__(dbobj)
# create the command- and scripthandlers as needed
try:
dummy = object.__getattribute__(dbobj, 'cmdset')
create_cmdset = type(dbobj.cmdset) != CmdSetHandler
except AttributeError:
create_cmdset = True
try:
dummy = object.__getattribute__(dbobj, 'scripts')
create_scripts = type(dbobj.scripts) != ScriptHandler
except AttributeError:
create_scripts = True
if create_cmdset:
dbobj.cmdset = CmdSetHandler(dbobj)
if create_scripts:
dbobj.scripts = ScriptHandler(dbobj)
def __eq__(self, other):
"""
This has be located at this level, having it in the
parent doesn't work.
"""
result = other and other.id == self.id
try:
uresult = other and (other.user.id == self.user.id)
except AttributeError:
uresult = False
return result or uresult
# hooks called by the game engine
def at_object_creation(self):
"""
Called once, when this object is first
created.
"""
pass
def at_first_login(self):
"""
Only called once, the very first
time the user logs in.
"""
pass
def at_pre_login(self):
"""
Called every time the user logs in,
before they are actually logged in.
"""
pass
def at_post_login(self):
"""
Called at the end of the login
process, just before letting
them loose.
"""
pass
def at_disconnect(self):
"""
Called just before user
is disconnected.
"""
pass
# hooks called when moving the object
def at_before_move(self, destination):
"""
Called just before starting to move
this object to destination.
destination - the object we are moving to
If this method returns False/None, the move
is cancelled before it is even started.
"""
#return has_perm(self, destination, "can_move")
return True
def announce_move_from(self, destination):
"""
Called if the move is to be announced. This is
called while we are still standing in the old
location.
destination - the place we are going to.
"""
if not self.location:
return
name = self.name
loc_name = ""
loc_name = self.location.name
dest_name = destination.name
string = "%s is leaving %s, heading for %s."
self.location.emit_to_contents(string % (name, loc_name, dest_name), exclude=self)
def announce_move_to(self, source_location):
"""
Called after the move if the move was not quiet. At this
point we are standing in the new location.
source_location - the place we came from
"""
name = self.name
if not source_location and self.location.has_player:
# This was created from nowhere and added to a player's
# inventory; it's probably the result of a create command.
string = "You now have %s in your possession." % name
self.location.emit_to(string)
return
src_name = "nowhere"
loc_name = self.location.name
if source_location:
src_name = source_location.name
string = "%s arrives to %s from %s."
self.location.emit_to_contents(string % (name, loc_name, src_name), exclude=self)
def at_after_move(self, source_location):
"""
Called after move has completed, regardless of quiet mode or not.
Allows changes to the object due to the location it is now in.
source_location - where we came from
"""
pass
def at_object_leave(self, moved_obj, target_location):
"""
Called just before an object leaves from inside this object
moved_obj - the object leaving
target_location - where the object is going.
"""
pass
def at_object_receive(self, moved_obj, source_location):
"""
Called after an object has been moved into this object.
moved_obj - the object moved into this one
source_location - where moved_object came from.
"""
pass
# hooks called by the default cmdset.
def return_appearance(self, pobject):
"""
This is a convenient hook for a 'look'
command to call.
"""
if not pobject:
return
string = "{c%s{n" % self.name
desc = self.attr("desc")
if desc:
string += ":\n %s" % desc
exits = []
users = []
things = []
for content in self.contents:
if content == pobject:
continue
name = content.name
if content.attr('_destination'):
exits.append(name)
elif content.has_player:
users.append(name)
else:
things.append(name)
if exits:
string += "\n{wExits:{n " + ", ".join(exits)
if users or things:
string += "\n{wYou see: {n"
if users:
string += "{c" + ", ".join(users) + "{n "
if things:
string += ", ".join(things)
return string
def at_msg_receive(self, msg, from_obj=None):
"""
This hook is called whenever someone
sends a message to this object.
Note that from_obj may be None if the sender did
not include itself as an argument to the obj.msg()
call - so you have to check for this. .
Consider this a pre-processing method before
msg is passed on to the user sesssion. If this
method returns False, the msg will not be
passed on.
msg = the message received
from_obj = the one sending the message
"""
return True
def at_msg_send(self, msg, to_obj):
"""
This is a hook that is called when /this/ object
sends a message to another object with obj.msg()
while also specifying that it is the one sending.
Note that this method is executed on the object
passed along with the msg() function (i.e. using
obj.msg(msg, caller) will then launch caller.at_msg())
and if no object was passed, it will never be called.
"""
pass
def at_desc(self, looker=None):
"""
This is called whenever someone looks
at this object. Looker is the looking
object.
"""
pass
def at_object_delete(self):
"""
Called just before the database object is
permanently delete()d from the database. If
this method returns False, deletion is aborted.
"""
return True
def at_get(self, getter):
"""
Called when this object has been picked up. Obs-
this method cannot stop the pickup - use permissions
for that!
getter - the object getting this object.
"""
pass
def at_drop(self, dropper):
"""
Called when this object has been dropped.
dropper - the object which just dropped this object.
"""
pass
def at_say(self, speaker, message):
"""
Called on this object if an object inside this object speaks.
The string returned from this method is the final form
of the speech. Obs - you don't have to add things like
'you say: ' or similar, that is handled by the say command.
speaker - the object speaking
message - the words spoken.
"""
return message
#
# Base Player object
#
class Character(Object):
"""
This is just like the Object except it implements its own
version of the at_object_creation to set up the script
that adds the default cmdset to the object.
"""
def at_object_creation(self):
"""
All this does (for now) is to add the default cmdset. Since
the script is permanently stored to this object (the permanent
keyword creates a script to do this), we should never need to
do this again for as long as this object exists.
"""
from settings import CMDSET_DEFAULT
self.cmdset.add_default(CMDSET_DEFAULT, permanent=True)
# this makes sure other objects are not accessing our
# command sets as they would any other object's sets.
self.cmdset.outside_access = False
def at_after_move(self, source_location):
"Default is to look around after a move."
self.execute_cmd('look')
#
# Base Room object
#
class Room(Object):
"""
This is the base room object. It's basically
like any Object except its location is None.
"""
def at_object_creation(self):
"""
Simple setup, shown as an example
(since default is None anyway)
"""
self.location = None
class Exit(Object):
"""
This is the base exit object - it connects a location
to another. What separates it from other objects
is that it has the '_destination' attribute defined.
Note that _destination is the only identifier to
separate an exit from normal objects, so if _destination
is removed, it will be treated like any other object. This
also means that any object can be made an exit by setting
the attribute _destination to a valid location
('Quack like a duck...' and so forth).
"""
def at_object_creation(self):
"""
Another example just for show; the _destination attribute
is usually set at creation time, not as part of the class
definition (unless you want an entire class of exits
all leadning to the same hard-coded place ...)
"""
self.attr("_destination", "None")
def at_object_delete(self):
"""
We have to make sure to clean the exithandler cache
when deleting the exit, or a new exit could be created
out of sync with the cache. You should do this also if
overloading this function in a child class.
"""
EXITHANDLER.reset(self.dbobj)
return True

View file

@ -1,29 +0,0 @@
"""
Utility functions for the Object class. These functions should not import
any models or modify the database.
"""
def is_dbref(dbstring, require_pound=True):
"""
Is the input a well-formed dbref number?
"""
# Strip the leading # sign if it's there.
if dbstring.startswith("#"):
dbstring = dbstring[1:]
else:
if require_pound:
# The pound sign was required and it didn't have it, fail out.
return False
try:
# If this fails, it's probably not valid.
number = int(dbstring)
except ValueError:
return False
except TypeError:
return False
# Numbers less than 1 are not valid dbrefs.
if number < 1:
return False
else:
return True

View file

@ -1 +0,0 @@
# Create your views here.