mirror of
https://github.com/evennia/evennia.git
synced 2026-03-30 20:47:17 +02:00
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:
parent
df29defbcd
commit
f83c2bddf8
222 changed files with 22304 additions and 14371 deletions
0
src/objects/__init__.py
Executable file → Normal file
0
src/objects/__init__.py
Executable file → Normal 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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
88
src/objects/exithandler.py
Normal file
88
src/objects/exithandler.py
Normal 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
388
src/objects/manager.py
Normal 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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
1896
src/objects/models.py
Executable file → Normal file
File diff suppressed because it is too large
Load diff
104
src/objects/object_search_funcs.py
Normal file
104
src/objects/object_search_funcs.py
Normal 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
376
src/objects/objects.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -1 +0,0 @@
|
|||
# Create your views here.
|
||||
Loading…
Add table
Add a link
Reference in a new issue