basicobject.py

---------------
  - 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.
This commit is contained in:
loki77 2008-06-13 18:15:54 +00:00
parent 87fb121427
commit ad009e20ab
8 changed files with 2634 additions and 2441 deletions

View file

@ -298,16 +298,23 @@ class Object(models.Model):
def set_description(self, new_desc): def set_description(self, new_desc):
""" """
Rename an object. Set an objects description
""" """
if new_desc == '':
self.description = None
else:
self.description = new_desc self.description = new_desc
self.save() self.save()
def get_description(self, no_parsing=False, wrap_text=False): def get_description(self, no_parsing=False, wrap_text=False):
""" """
Returns an object's ANSI'd description. Returns an object's ANSI'd description or None if description is not
set.
""" """
try: try:
# If no description is set, return None
if self.description is None:
return None
# Evaluate ANSI and stuff? # Evaluate ANSI and stuff?
if no_parsing: if no_parsing:
retval = self.description retval = self.description
@ -321,7 +328,7 @@ class Object(models.Model):
return retval return retval
except: except:
# No description attribute present, return empty string. # No description attribute present, return empty string.
return "" return None
def get_flags(self): def get_flags(self):
""" """
@ -741,17 +748,17 @@ class Object(models.Model):
# Type comparison methods. # Type comparison methods.
def is_player(self): def is_player(self):
return self.type == 1 return self.type == defines_global.OTYPE_PLAYER
def is_room(self): def is_room(self):
return self.type == 2 return self.type == defines_global.OTYPE_ROOM
def is_thing(self): def is_thing(self):
return self.type == 3 return self.type == defines_global.OTYPE_THING
def is_exit(self): def is_exit(self):
return self.type == 4 return self.type == defines_global.OTYPE_EXIT
def is_going(self): def is_going(self):
return self.type == 5 return self.type == defines_global.OTYPE_GOING
def is_garbage(self): def is_garbage(self):
return self.type == 6 return self.type == defines_global.OTYPE_GARBAGE
def get_type(self, return_number=False): def get_type(self, return_number=False):
""" """

View file

@ -25,6 +25,79 @@ def match_exits(pobject, searchstr):
exits = pobject.get_location().get_contents(filter_type=4) exits = pobject.get_location().get_contents(filter_type=4)
return functions_db.list_search_object_namestr(exits, searchstr, match_type="exact") return functions_db.list_search_object_namestr(exits, searchstr, match_type="exact")
def parse_command(command_string):
"""
Tries to handle the most common command strings and returns a dictionary with various data.
Common command types:
- Complex:
@pemit[/option] <target>[/option]=<data>
- Simple:
look
look <target>
I'm not married to either of these terms, but I couldn't think of anything better. If you can, lets change it :)
The only cases that I haven't handled is if someone enters something like:
@pemit <target> <target>/<switch>=<data>
- Ends up considering both targets as one with a space between them, and the switch as a switch.
@pemit <target>/<switch> <target>=<data>
- Ends up considering the first target a target, and the second target as part of the switch.
"""
# Each of the bits of data starts off as None, except for the raw, original
# command
parsed_command = dict(
raw_command=command_string,
data=None,
original_command=None,
original_targets=None,
base_command=None,
command_switches=None,
targets=None,
target_switches=None
)
try:
# If we make it past this next statement, then this is what we
# consider a complex command
(command_parts, data) = command_string.split('=', 1)
parsed_command['data'] = data
# First we deal with the command part of the command and break it
# down into the base command, along with switches
# If we make it past the next statement, then they must have
# entered a command like:
# p =<data>
# So we should probably just let it get caught by the ValueError
# again and consider it a simple command
(total_command, total_targets) = command_parts.split(' ', 1)
parsed_command['original_command'] = total_command
parsed_command['original_targets'] = total_targets
split_command = total_command.split('/')
parsed_command['base_command'] = split_command[0]
parsed_command['command_switches'] = split_command[1:]
# Now we move onto the target data
try:
# Look for switches- if they give target switches, then we don't
# accept multiple targets
(target, switch_string) = total_targets.split('/', 1)
parsed_command['targets'] = [target]
parsed_command['target_switches'] = switch_string.split('/')
except ValueError:
# Alright, no switches, so lets consider multiple targets
parsed_command['targets'] = total_targets.split()
except ValueError:
# Ok, couldn't find an =, so not a complex command
try:
(command, data) = command_string.split(' ', 1)
parsed_command['base_command'] = command
parsed_command['data'] = data
except ValueError:
# No arguments
# ie:
# - look
parsed_command['base_command'] = command_string
return parsed_command
def handle(cdat): def handle(cdat):
""" """
Use the spliced (list) uinput variable to retrieve the correct Use the spliced (list) uinput variable to retrieve the correct
@ -40,17 +113,19 @@ def handle(cdat):
try: try:
# TODO: Protect against non-standard characters. # TODO: Protect against non-standard characters.
if cdat['uinput'] == '': if cdat['uinput'] == '':
raise UnknownCommand return
uinput = cdat['uinput'].split()
parsed_input = {} parsed_input = {}
parsed_input['parsed_command'] = parse_command(cdat['uinput'])
# First we split the input up by spaces. # First we split the input up by spaces.
parsed_input['splitted'] = uinput parsed_input['splitted'] = cdat['uinput'].split()
# Now we find the root command chunk (with switches attached). # Now we find the root command chunk (with switches attached).
parsed_input['root_chunk'] = parsed_input['splitted'][0].split('/') parsed_input['root_chunk'] = parsed_input['splitted'][0].split('/')
# And now for the actual root command. It's the first entry in root_chunk. # And now for the actual root command. It's the first entry in root_chunk.
parsed_input['root_cmd'] = parsed_input['root_chunk'][0].lower() parsed_input['root_cmd'] = parsed_input['root_chunk'][0].lower()
# Keep around the full, raw input in case a command needs it
cdat['raw_input'] = cdat['uinput']
# Now we'll see if the user is using an alias. We do a dictionary lookup, # Now we'll see if the user is using an alias. We do a dictionary lookup,
# if the key (the player's root command) doesn't exist on the dict, we # if the key (the player's root command) doesn't exist on the dict, we

View file

@ -58,6 +58,7 @@ ctable = {
"@destroy": (commands.objmanip.cmd_destroy, ("genperms.builder")), "@destroy": (commands.objmanip.cmd_destroy, ("genperms.builder")),
"@dig": (commands.objmanip.cmd_dig, ("genperms.builder")), "@dig": (commands.objmanip.cmd_dig, ("genperms.builder")),
"@emit": (commands.general.cmd_emit, ("genperms.announce")), "@emit": (commands.general.cmd_emit, ("genperms.announce")),
# "@pemit": (commands.general.cmd_pemit, None),
"@find": (commands.objmanip.cmd_find, ("genperms.builder")), "@find": (commands.objmanip.cmd_find, ("genperms.builder")),
"@link": (commands.objmanip.cmd_link, ("genperms.builder")), "@link": (commands.objmanip.cmd_link, ("genperms.builder")),
"@list": (commands.info.cmd_list, ("genperms.process_control")), "@list": (commands.info.cmd_list, ("genperms.process_control")),
@ -86,5 +87,4 @@ def return_cmdtuple(func_name, unlogged_cmd=False):
cfunc = ctable.get(func_name, False) cfunc = ctable.get(func_name, False)
else: else:
cfunc = uncon_ctable.get(func_name, False) cfunc = uncon_ctable.get(func_name, False)
return cfunc return cfunc

View file

@ -286,47 +286,114 @@ def cmd_page(cdat):
pobject = session.get_pobject() pobject = session.get_pobject()
server = cdat['server'] server = cdat['server']
args = cdat['uinput']['splitted'][1:] args = cdat['uinput']['splitted'][1:]
parsed_command = cdat['uinput']['parsed_command']
# We use a dict to ensure that the list of targets is unique
targets = dict()
# Get the last paged person
last_paged_dbrefs = pobject.get_attribute_value("LASTPAGED")
# If they have paged someone before, go ahead and grab the object of
# that person.
if last_paged_dbrefs is not False:
last_paged_objects = list()
try:
last_paged_dbref_list = [
x.strip() for x in last_paged_dbrefs.split(',')]
for dbref in last_paged_dbref_list:
if not functions_db.is_dbref(dbref):
raise ValueError
last_paged_object = functions_db.dbref_search(dbref)
if last_paged_object is not None:
last_paged_objects.append(last_paged_object)
except ValueError:
# LASTPAGED Attribute is not a list of dbrefs
last_paged_dbrefs = False
# Remove the invalid LASTPAGED attribute
pobject.clear_attribute("LASTPAGED")
if len(args) == 0: # If they don't give a target, or any data to send to the target
session.msg("Page who/what?") # then tell them who they last paged if they paged someone, if not
# tell them they haven't paged anyone.
if parsed_command['targets'] is None and parsed_command['data'] is None:
if last_paged_dbrefs is not False and not last_paged_objects == list():
session.msg("You last paged: %s." % (
', '.join([x.name for x in last_paged_objects])))
return
session.msg("You have not paged anyone.")
return return
# Combine the arguments into one string, split it by equal signs into # Build a list of targets
# victim (entry 0 in the list), and message (entry 1 and above). # If there are no targets, then set the targets to the last person they
eq_args = ' '.join(args).split('=') # paged.
if parsed_command['targets'] is None:
# If no equal sign is in the passed arguments, see if the player has if not last_paged_objects == list():
# a LASTPAGED attribute. If they do, default the page to them, if not, targets = dict([(target, 1) for target in last_paged_objects])
# don't touch anything and error out.
if len(eq_args) == 1 and pobject.has_attribute("LASTPAGED"):
eq_args.insert(0, "#%s" % (pobject.get_attribute_value("LASTPAGED"),))
if len(eq_args) > 1:
target = functions_db.player_search(pobject, eq_args[0])
message = ' '.join(eq_args[1:])
if len(target) == 0:
session.msg("I don't recognize \"%s\"." % (eq_args[0].capitalize(),))
return
elif len(message) == 0:
session.msg("I need a message to deliver.")
return
elif len(target) > 1:
session.msg("Try a more unique spelling of their name.")
return
else: else:
if target[0].is_connected_plr(): # First try to match the entire target string against a single player
target[0].emit_to("%s pages: %s" % full_target_match = functions_db.player_name_search(
(pobject.get_name(show_dbref=False), message)) parsed_command['original_targets'])
session.msg("You paged %s with '%s'." % if full_target_match is not None:
(target[0].get_name(show_dbref=False), message)) targets[full_target_match] = 1
pobject.set_attribute("LASTPAGED", target[0].id)
else: else:
session.msg("Player %s does not exist or is not online." % # For each of the targets listed, grab their objects and append
(target[0].get_name(show_dbref=False),)) # it to the targets list
for target in parsed_command['targets']:
# If the target is a dbref, behave appropriately
if functions_db.is_dbref(target):
session.msg("Is dbref.")
matched_object = functions_db.dbref_search(target,
limit_types=[defines_global.OTYPE_PLAYER])
if matched_object is not None:
targets[matched_object] = 1
else: else:
session.msg("Page who?") # search returned None
return session.msg("Player '%s' does not exist." % (
target))
else:
# Not a dbref, so must be a username, treat it as such
matched_object = functions_db.player_name_search(
target)
if matched_object is not None:
targets[matched_object] = 1
else:
# search returned None
session.msg("Player '%s' does not exist." % (
target))
data = parsed_command['data']
sender_name = pobject.get_name(show_dbref=False)
# Build our messages
target_message = "%s pages: %s"
sender_message = "You paged %s with '%s'."
# Handle paged emotes
if data.startswith(':'):
data = data[1:]
target_message = "From afar, %s %s"
sender_message = "Long distance to %s: %s %s"
# Handle paged emotes without spaces
if data.startswith(';'):
data = data[1:]
target_message = "From afar, %s%s"
sender_message = "Long distance to %s: %s%s"
# We build a list of target_names for the sender_message later
target_names = []
for target in targets.keys():
# Check to make sure they're connected, or a player
if target.is_connected_plr():
target.emit_to(target_message % (sender_name, data))
target_names.append(target.get_name(show_dbref=False))
else:
session.msg("Player %s does not exist or is not online." % (
target.get_name(show_dbref=False)))
if len(target_names) > 0:
target_names_string = ', '.join(target_names)
try:
session.msg(sender_message % (target_names_string, sender_name, data))
except TypeError:
session.msg(sender_message % (target_names_string, data))
# Now set the LASTPAGED attribute
pobject.set_attribute("LASTPAGED", ','.join(
["#%d" % (x.id) for x in targets.keys()]))
def cmd_quit(cdat): def cmd_quit(cdat):
""" """

View file

@ -1,6 +1,8 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from apps.objects.models import Attribute, Object
import functions_db import functions_db
import functions_general import functions_general
import defines_global
""" """
Commands that are available from the connect screen. Commands that are available from the connect screen.
@ -70,8 +72,13 @@ def cmd_create(cdat):
account = User.objects.filter(username=uname) account = User.objects.filter(username=uname)
# Match an email address to an account. # Match an email address to an account.
email_matches = functions_db.get_user_from_email(email) email_matches = functions_db.get_user_from_email(email)
# Look for any objects with an 'Alias' attribute that matches
# the requested username
alias_matches = Object.objects.filter(attribute__attr_name__exact="ALIAS",
attribute__attr_value__iexact=uname).filter(
type=defines_global.OTYPE_PLAYER)
if not account.count() == 0: if not account.count() == 0 or not alias_matches.count() == 0:
session.msg("There is already a player with that name!") session.msg("There is already a player with that name!")
elif not email_matches.count() == 0: elif not email_matches.count() == 0:
session.msg("There is already a player with that email address!") session.msg("There is already a player with that email address!")

View file

@ -4,8 +4,9 @@ from datetime import datetime, timedelta
from django.db import connection from django.db import connection
from django.contrib.auth.models import User from django.contrib.auth.models import User
from apps.objects.models import Object, Attribute from apps.objects.models import Object, Attribute
import defines_global as defines_global import defines_global
import gameconf import gameconf
from django.db.models import Q
""" """
Common database functions. Common database functions.
@ -115,19 +116,6 @@ def list_search_object_namestr(searchlist, ostring, dbref_only=False, limit_type
else: else:
return [prospect for prospect in searchlist if prospect.name_match(ostring, match_type=match_type)] return [prospect for prospect in searchlist if prospect.name_match(ostring, match_type=match_type)]
def player_search(searcher, ostring):
"""
Combines an alias and local/global search for a player's name. If there are
no alias matches, do a global search limiting by type PLAYER.
searcher: (Object) The object doing the searching.
ostring: (string) The alias string to search for.
"""
alias_results = alias_search(searcher, ostring)
if len(alias_results) > 0:
return alias_results
else:
return local_and_global_search(searcher, ostring, limit_types=[defines_global.OTYPE_PLAYER])
def standard_plr_objsearch(session, ostring, search_contents=True, search_location=True, dbref_only=False, limit_types=False): def standard_plr_objsearch(session, ostring, search_contents=True, search_location=True, dbref_only=False, limit_types=False):
""" """
@ -173,9 +161,54 @@ def alias_search(searcher, ostring):
ostring: (string) The alias string to search for. ostring: (string) The alias string to search for.
""" """
search_query = ''.join(ostring) search_query = ''.join(ostring)
results = Attribute.objects.select_related().filter(attr_value__iexact=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()] 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): 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. Searches an object's location then globally for a dbref or name match.
@ -192,18 +225,11 @@ def local_and_global_search(searcher, ostring, search_contents=True, search_loca
# This is a global dbref search. Not applicable if we're only searching # This is a global dbref search. Not applicable if we're only searching
# searcher's contents/locations, dbref comparisons for location/contents # searcher's contents/locations, dbref comparisons for location/contents
# searches are handled by list_search_object_namestr() below. # searches are handled by list_search_object_namestr() below.
if is_dbref(ostring) and search_contents and search_location: if is_dbref(ostring):
search_num = search_query[1:] search_num = search_query[1:]
dbref_results = Object.objects.filter(id=search_num).exclude(type=6) dbref_match = dbref_search(search_num, limit_types)
if dbref_match is not None:
# If there is a type limiter in, filter by it. return [dbref_match]
if limit_types:
for limiter in limit_types:
dbref_results.filter(type=limiter)
dbref_match = list(dbref_results)
if len(dbref_match) > 0:
return dbref_match
# If the search string is one of the following, return immediately with # If the search string is one of the following, return immediately with
# the appropriate result. # the appropriate result.
@ -212,6 +238,13 @@ def local_and_global_search(searcher, ostring, search_contents=True, search_loca
elif ostring == 'me' and searcher: elif ostring == 'me' and searcher:
return [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 = [] local_matches = []
# Handle our location/contents searches. list_search_object_namestr() does # Handle our location/contents searches. list_search_object_namestr() does
# name and dbref comparisons against search_query. # name and dbref comparisons against search_query.
@ -219,7 +252,6 @@ def local_and_global_search(searcher, ostring, search_contents=True, search_loca
local_matches += list_search_object_namestr(searcher.get_contents(), search_query, limit_types) local_matches += list_search_object_namestr(searcher.get_contents(), search_query, limit_types)
if search_location: if search_location:
local_matches += list_search_object_namestr(searcher.get_location().get_contents(), search_query, limit_types=limit_types) local_matches += list_search_object_namestr(searcher.get_location().get_contents(), search_query, limit_types=limit_types)
return local_matches return local_matches
def is_dbref(dbstring): def is_dbref(dbstring):
@ -230,8 +262,7 @@ def is_dbref(dbstring):
number = int(dbstring[1:]) number = int(dbstring[1:])
except ValueError: except ValueError:
return False return False
if not dbstring.startswith("#"):
if dbstring[0] != '#':
return False return False
elif number < 1: elif number < 1:
return False return False

View file

@ -34,10 +34,16 @@ class BasicObject:
""" """
target_obj = self.source_obj target_obj = self.source_obj
pobject = values["pobject"] pobject = values["pobject"]
retval = "\r\n%s\r\n%s" % ( description = target_obj.get_description()
if description is not None:
retval = "%s\r\n%s" % (
target_obj.get_name(), target_obj.get_name(),
target_obj.get_description(), target_obj.get_description(),
) )
else:
retval = "%s" % (
target_obj.get_name(),
)
con_players = [] con_players = []
con_things = [] con_things = []
@ -52,15 +58,15 @@ class BasicObject:
else: else:
con_things.append(obj) con_things.append(obj)
if con_players: if not con_players == []:
retval += "\n\r%sPlayers:%s" % (ansi.ansi["hilite"], ansi.ansi["normal"],) retval += "\n\r%sPlayers:%s" % (ansi.ansi["hilite"], ansi.ansi["normal"],)
for player in con_players: for player in con_players:
retval +='\n\r%s' %(player.get_name(),) retval +='\n\r%s' %(player.get_name(),)
if con_things: if not con_things == []:
retval += "\n\r%sContents:%s" % (ansi.ansi["hilite"], ansi.ansi["normal"],) retval += "\n\r%sContents:%s" % (ansi.ansi["hilite"], ansi.ansi["normal"],)
for thing in con_things: for thing in con_things:
retval += '\n\r%s' %(thing.get_name(),) retval += '\n\r%s' %(thing.get_name(),)
if con_exits: if not con_exits == []:
retval += "\n\r%sExits:%s" % (ansi.ansi["hilite"], ansi.ansi["normal"],) retval += "\n\r%sExits:%s" % (ansi.ansi["hilite"], ansi.ansi["normal"],)
for exit in con_exits: for exit in con_exits:
retval += '\n\r%s' %(exit.get_name(),) retval += '\n\r%s' %(exit.get_name(),)