mirror of
https://github.com/evennia/evennia.git
synced 2026-03-19 22:36:31 +01:00
---------------
- Checks for NULL description on objects- if Null, it doesn't print the extra line any more.
- Made the checks for contents a little less ambiguous
cmdhandler.py
--------------
- Added new method 'parse_command' which takes a command string and tries to break it up based on common command parsing rules. Mostly complete, but could use some work on the edge cases. Check out the docstring on the function- I tried to make it fairly well documented.
- Changed the check for 'non-standard characters' to just return, rather than throw an Exception. Not sure if this causes any issues, but I noticed that when you hit enter without entering a command it would trigger this code. Now it just fails silently.
- The handle function now calls the parse_command function now and stores the results in parsed_input['parsed_command']. This then gets put into cdat['uinput'] at the end of handle() like before. The old data in parsed_input is still there, this is just a new field.
- Added cdat['raw_input'] to pass the full, untouched command string on. This is also stored in parsed_input['parsed_command']['raw_command'] so not sure fi this is necessary any longer, probably not.
cmdtable.py
------------
- Just cleaned it up a bit and straightened out the columns after changing 3-4 space indentation.
apps/objects/models.py
-----------------------
- set_description now sets the description attribute to 'None' (or Null in the db) when given a blank description. This is used for the change mentioned above in basicobject.py
- get_description now returns None if self.description is None
- used defines_global in the comparison methods like is_player
functions_db.py
----------------
- Changed import defines_global as defines_global to just 'import defines_global'- wasn't sure why this was this way, if I broke something (I didn't seem to) let me know.
- renamed player_search to player_name_search. Removed the use of local_and_global_search inside of it. local_and_global_search now calls it when it receives a search_string that starts with *.
- alias_search now only looks at attributes with attr_name == ALIAS. It used to just look at attr_value, which could match anything, it seemed.
- added 'dbref_search'
- local_and_global_search changes:
- Now uses dbref_search & player_search if the string starts with "#" or "*" respectively
- Changed when it uses dbref_search to whenever the search_string is a dbref. It used to check that it was a dbref, and that search_contents & search_location were set, but I *believe* in most MU*'s when you supply a dbref it never fails to find the object.
commands/unloggedin.py
-----------------------
- removed hardcoded object type #'s and started using defines_global instead
- when creating a new account, made sure that no object with an alias matching the player name requested exists. This is behavior from TinyMUSH, and I think most MUSHs follow this, but if not this is easy enough to change back.
commands/general.py
--------------------
- Rewrote cmd_page:
- New Features
- Page by dbref
- Page multiple people
- pose (:) and no space pose (;) pages
- When someone hits page without a target or data, it now will tell the player who they last paged, or say they haven't paged anyone if they don't have a LASTPAGED
- uses parse_command, made it a lot easier to work through the extra functionality added above
- When there are multiple words in a page target, it first tries to find a player that matches the entire string. If that fails, then it goes through each word, assuming each is a separate target, and works out paging them.
commands/objmanip.py
---------------------
- I started to muck with cmd_name & cmd_page, but decided to hold off for now. Largely, if everyone is cool with the idea that names & aliases should be totally unique, then we need to go ahead and re-write these. I'll do that if everyone is cool with it.
375 lines
13 KiB
Python
375 lines
13 KiB
Python
import sets
|
|
from datetime import datetime, timedelta
|
|
|
|
from django.db import connection
|
|
from django.contrib.auth.models import User
|
|
from apps.objects.models import Object, Attribute
|
|
import defines_global
|
|
import gameconf
|
|
from django.db.models import Q
|
|
|
|
"""
|
|
Common database functions.
|
|
"""
|
|
def num_total_players():
|
|
"""
|
|
Returns the total number of registered players.
|
|
"""
|
|
return User.objects.count()
|
|
|
|
def get_connected_players():
|
|
"""
|
|
Returns the a QuerySet containing the currently connected players.
|
|
"""
|
|
return Object.objects.filter(nosave_flags__contains="CONNECTED")
|
|
|
|
def get_recently_created_players(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_players(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 is_unsavable_flag(flagname):
|
|
"""
|
|
Returns TRUE if the flag is an unsavable flag.
|
|
"""
|
|
return flagname.upper() in defines_global.NOSAVE_FLAGS
|
|
|
|
def is_modifiable_flag(flagname):
|
|
"""
|
|
Check to see if a particular flag is modifiable.
|
|
"""
|
|
if flagname.upper() not in defines_global.NOSET_FLAGS:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def is_modifiable_attrib(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
|
|
|
|
def get_nextfree_dbnum():
|
|
"""
|
|
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 = Object.objects.filter(type__exact=defines_global.OTYPE_GARBAGE)
|
|
if nextfree:
|
|
# We've got at least one garbage object to recycle.
|
|
return nextfree.id
|
|
else:
|
|
# No garbage to recycle, find the highest dbnum and increment it
|
|
# for our next free.
|
|
return int(Object.objects.order_by('-id')[0].id + 1)
|
|
|
|
def global_object_name_search(ostring, exact_match=False):
|
|
"""
|
|
Searches through all objects for a name match.
|
|
"""
|
|
if exact_match:
|
|
return Object.objects.filter(name__iexact=ostring).exclude(type=defines_global.OTYPE_GARBAGE)
|
|
else:
|
|
return Object.objects.filter(name__icontains=ostring).exclude(type=defines_global.OTYPE_GARBAGE)
|
|
|
|
def list_search_object_namestr(searchlist, ostring, dbref_only=False, limit_types=False, match_type="fuzzy"):
|
|
"""
|
|
Iterates through a list of objects and returns a list of
|
|
name matches.
|
|
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.
|
|
"""
|
|
if dbref_only:
|
|
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)]
|
|
else:
|
|
if limit_types:
|
|
return [prospect for prospect in searchlist if prospect.name_match(ostring, match_type=match_type) and prospect.type in limit_types]
|
|
else:
|
|
return [prospect for prospect in searchlist if prospect.name_match(ostring, match_type=match_type)]
|
|
|
|
|
|
def standard_plr_objsearch(session, ostring, search_contents=True, search_location=True, dbref_only=False, limit_types=False):
|
|
"""
|
|
Perform a standard object search via a player session, handling multiple
|
|
results and lack thereof gracefully.
|
|
|
|
session: (SessionProtocol) Reference to the player's session.
|
|
ostring: (str) The string to match object names against.
|
|
"""
|
|
pobject = session.get_pobject()
|
|
results = local_and_global_search(pobject, ostring, search_contents=search_contents, search_location=search_location, dbref_only=dbref_only, limit_types=limit_types)
|
|
|
|
if len(results) > 1:
|
|
session.msg("More than one match found (please narrow target):")
|
|
for result in results:
|
|
session.msg(" %s" % (result.get_name(),))
|
|
return False
|
|
elif len(results) == 0:
|
|
session.msg("I don't see that here.")
|
|
return False
|
|
else:
|
|
return results[0]
|
|
|
|
def object_totals():
|
|
"""
|
|
Returns a dictionary with database object totals.
|
|
"""
|
|
dbtotals = {}
|
|
dbtotals["objects"] = Object.objects.count()
|
|
dbtotals["things"] = Object.objects.filter(type=defines_global.OTYPE_THING).count()
|
|
dbtotals["exits"] = Object.objects.filter(type=defines_global.OTYPE_EXIT).count()
|
|
dbtotals["rooms"] = Object.objects.filter(type=defines_global.OTYPE_ROOM).count()
|
|
dbtotals["garbage"] = Object.objects.filter(type=defines_global.OTYPE_GARBAGE).count()
|
|
dbtotals["players"] = Object.objects.filter(type=defines_global.OTYPE_PLAYER).count()
|
|
return dbtotals
|
|
|
|
def alias_search(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.
|
|
"""
|
|
search_query = ''.join(ostring)
|
|
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(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 = Object.objects.filter(
|
|
player_filter | alias_filter).filter(
|
|
type=defines_global.OTYPE_PLAYER).distinct()
|
|
try:
|
|
return player_matches[0]
|
|
except IndexError:
|
|
return None
|
|
|
|
def dbref_search(dbref_string, limit_types=False):
|
|
"""
|
|
Searches for a given dbref.
|
|
|
|
dbref_number: (string) The dbref to search for
|
|
limit_types: (list of int) A list of Object type numbers to filter by.
|
|
"""
|
|
if not is_dbref(dbref_string):
|
|
return None
|
|
dbref_string = dbref_string[1:]
|
|
dbref_matches = Object.objects.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 local_and_global_search(searcher, ostring, search_contents=True, search_location=True, dbref_only=False, limit_types=False):
|
|
"""
|
|
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.
|
|
"""
|
|
search_query = ''.join(ostring)
|
|
|
|
# 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 is_dbref(ostring):
|
|
search_num = search_query[1:]
|
|
dbref_match = dbref_search(search_num, 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[0] == "*":
|
|
# Player search- gotta search by name or alias
|
|
search_target = search_query[1:]
|
|
player_match = player_name_search(search_target)
|
|
if player_match is not None:
|
|
return [player_match]
|
|
|
|
local_matches = []
|
|
# Handle our location/contents searches. list_search_object_namestr() does
|
|
# name and dbref comparisons against search_query.
|
|
if search_contents:
|
|
local_matches += list_search_object_namestr(searcher.get_contents(), search_query, limit_types)
|
|
if search_location:
|
|
local_matches += list_search_object_namestr(searcher.get_location().get_contents(), search_query, limit_types=limit_types)
|
|
return local_matches
|
|
|
|
def is_dbref(dbstring):
|
|
"""
|
|
Is the input a well-formed dbref number?
|
|
"""
|
|
try:
|
|
number = int(dbstring[1:])
|
|
except ValueError:
|
|
return False
|
|
if not dbstring.startswith("#"):
|
|
return False
|
|
elif number < 1:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def get_user_from_email(uemail):
|
|
"""
|
|
Returns a player's User object when given an email address.
|
|
"""
|
|
return User.objects.filter(email__iexact=uemail)
|
|
|
|
def get_object_from_dbref(dbref):
|
|
"""
|
|
Returns an object when given a dbref.
|
|
"""
|
|
return Object.objects.get(id=dbref)
|
|
|
|
def create_object(odat):
|
|
"""
|
|
Create a new object. odat is a dictionary that contains the following keys.
|
|
REQUIRED KEYS:
|
|
* 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.
|
|
OPTIONAL KEYS:
|
|
* home: Reference to another object to home to. If not specified, use
|
|
location key for home.
|
|
"""
|
|
next_dbref = get_nextfree_dbnum()
|
|
new_object = Object()
|
|
|
|
new_object.id = next_dbref
|
|
new_object.type = odat["type"]
|
|
new_object.set_name(odat["name"])
|
|
|
|
# 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 odat["type"] == 1:
|
|
new_object.owner = None
|
|
new_object.zone = None
|
|
else:
|
|
new_object.owner = odat["owner"]
|
|
|
|
if new_object.get_owner().get_zone():
|
|
new_object.zone = new_object.get_owner().get_zone()
|
|
|
|
# If we have a 'home' key, use that for our home value. Otherwise use
|
|
# the location key.
|
|
if odat.has_key("home"):
|
|
new_object.home = odat["home"]
|
|
else:
|
|
if new_object.is_exit():
|
|
new_object.home = None
|
|
else:
|
|
new_object.home = odat["location"]
|
|
|
|
new_object.save()
|
|
|
|
# Rooms have a NULL location.
|
|
if not new_object.is_room():
|
|
new_object.move_to(odat['location'])
|
|
|
|
return new_object
|
|
|
|
def create_user(cdat, uname, email, password):
|
|
"""
|
|
Handles the creation of new users.
|
|
"""
|
|
session = cdat['session']
|
|
server = cdat['server']
|
|
start_room = int(gameconf.get_configvalue('player_dbnum_start'))
|
|
start_room_obj = 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 = get_nextfree_dbnum()
|
|
print 'UID', uid
|
|
|
|
# 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
|
|
print 'UID2', 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))
|
|
|
|
# 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.
|
|
odat = {"id": uid, "name": uname, "type": 1, "location": start_room_obj, "owner": None}
|
|
user_object = create_object(odat)
|
|
|
|
# Activate the player's session and set them loose.
|
|
session.login(user)
|
|
print 'Registration: %s' % (session,)
|
|
session.msg("Welcome to %s, %s.\n\r" % (gameconf.get_configvalue('site_name'), session.get_pobject().get_name(show_dbref=False),))
|