Make ic command better handle multiple-matches.

Resolves #1923. This changes the `ic` command so non-privileged
users will search through their `_playable_characters` Attribute list.

Privileged (Builder+) users will use their `_playable_characters` list,
but if they are already puppeting a char in the same location as an
object with the given name, this will be used instead. Only if no match
is found neither in `_playable_characters` nor in the current location
will a global search for a puppetable target be done (and only for
Builders+)
This commit is contained in:
Griatch 2020-07-20 22:12:49 +02:00
parent fa09aeef50
commit 87c7e169b1
4 changed files with 94 additions and 36 deletions

View file

@ -941,6 +941,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
nofound_string=None, nofound_string=None,
multimatch_string=None, multimatch_string=None,
use_nicks=True, use_nicks=True,
quiet=False,
**kwargs, **kwargs,
): ):
""" """
@ -967,9 +968,13 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
message to echo if `searchdata` leads to multiple matches. message to echo if `searchdata` leads to multiple matches.
If not given, will fall back to the default handler. If not given, will fall back to the default handler.
use_nicks (bool, optional): Use account-level nick replacement. use_nicks (bool, optional): Use account-level nick replacement.
quiet (bool, optional): If set, will not show any error to the user,
and will also lead to returning a list of matches.
Return: Return:
match (Account, Object or None): A single Account or Object match. match (Account, Object or None): A single Account or Object match.
list: If `quiet=True` this is a list of 0, 1 or more Account or Object matches.
Notes: Notes:
Extra keywords are ignored, but are allowed in call in Extra keywords are ignored, but are allowed in call in
order to make API more consistent with order to make API more consistent with
@ -981,28 +986,33 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# handle wrapping of common terms # handle wrapping of common terms
if searchdata.lower() in ("me", "*me", "self", "*self"): if searchdata.lower() in ("me", "*me", "self", "*self"):
return self return self
searchdata = self.nicks.nickreplace(
searchdata, categories=("account",), include_account=False
)
if search_object: if search_object:
matches = ObjectDB.objects.object_search( matches = ObjectDB.objects.object_search(
searchdata, typeclass=typeclass, use_nicks=use_nicks searchdata, typeclass=typeclass
) )
else: else:
searchdata = self.nicks.nickreplace(
searchdata, categories=("account",), include_account=False
)
matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass) matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass)
matches = _AT_SEARCH_RESULT(
matches, if quiet:
self, matches = list(matches)
query=searchdata, if return_puppet:
nofound_string=nofound_string, matches = [match.puppet for match in matches]
multimatch_string=multimatch_string, else:
) matches = _AT_SEARCH_RESULT(
if matches and return_puppet: matches,
try: self,
return matches.puppet query=searchdata,
except AttributeError: nofound_string=nofound_string,
return None multimatch_string=multimatch_string,
)
if matches and return_puppet:
try:
matches = matches.puppet
except AttributeError:
return None
return matches return matches
def access( def access(

View file

@ -301,27 +301,60 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
session = self.session session = self.session
new_character = None new_character = None
character_candidates = []
if not self.args: if not self.args:
new_character = account.db._last_puppet character_candidates = [account.db._last_puppet] or []
if not new_character: if not character_candidates:
self.msg("Usage: ic <character>") self.msg("Usage: ic <character>")
return return
if not new_character: else:
# search for a matching character # argument given
new_character = [
char for char in search.object_search(self.args) if char.access(account, "puppet") if account.db._playable_characters:
] # look at the playable_characters list first
if not new_character: character_candidates.extend(
self.msg("That is not a valid character choice.") account.search(self.args, candidates=account.db._playable_characters,
return search_object=True, quiet=True)
if len(new_character) > 1:
self.msg(
"Multiple targets with the same name:\n %s"
% ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character)
) )
return
else: if account.locks.check_lockstring(account, "perm(Builder)"):
new_character = new_character[0] # builders and higher should be able to puppet more than their
# playable characters.
if session.puppet:
# start by local search - this helps to avoid the user
# getting locked into their playable characters should one
# happen to be named the same as another. We replace the suggestion
# from playable_characters here - this allows builders to puppet objects
# with the same name as their playable chars should it be necessary
# (by going to the same location).
character_candidates = [
char
for char in session.puppet.search(self.args, quiet=True)
if char.access(account, "puppet")
]
if not character_candidates:
# fall back to global search only if Builder+ has no
# playable_characers in list and is not standing in a room
# with a matching char.
character_candidates.extend([
char for char in search.object_search(self.args) if char.access(account, "puppet")]
)
# handle possible candidates
if not character_candidates:
self.msg("That is not a valid character choice.")
return
if len(character_candidates) > 1:
self.msg(
"Multiple targets with the same name:\n %s"
% ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in character_candidates)
)
return
else:
new_character = character_candidates[0]
# do the puppet puppet
try: try:
account.puppet_object(session, new_character) account.puppet_object(session, new_character)
account.db._last_puppet = new_character account.db._last_puppet = new_character

View file

@ -353,11 +353,27 @@ class TestAccount(CommandTest):
self.call(account.CmdOOC(), "", "You go OOC.", caller=self.account) self.call(account.CmdOOC(), "", "You go OOC.", caller=self.account)
def test_ic(self): def test_ic(self):
self.account.db._playable_characters = [self.char1]
self.account.unpuppet_object(self.session) self.account.unpuppet_object(self.session)
self.call( self.call(
account.CmdIC(), "Char", "You become Char.", caller=self.account, receiver=self.char1 account.CmdIC(), "Char", "You become Char.", caller=self.account, receiver=self.char1
) )
def test_ic__other_object(self):
self.account.db._playable_characters = [self.obj1]
self.account.unpuppet_object(self.session)
self.call(
account.CmdIC(), "Obj", "You become Obj.", caller=self.account, receiver=self.obj1
)
def test_ic__nonaccess(self):
self.account.unpuppet_object(self.session)
self.call(
account.CmdIC(), "Nonexistent", "That is not a valid character choice.",
caller=self.account, receiver=self.account
)
def test_password(self): def test_password(self):
self.call( self.call(
account.CmdPassword(), account.CmdPassword(),

View file

@ -389,8 +389,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
- `me,self`: self-reference to this object - `me,self`: self-reference to this object
- `<num>-<string>` - can be used to differentiate - `<num>-<string>` - can be used to differentiate
between multiple same-named matches between multiple same-named matches
global_search (bool): Search all objects globally. This is overruled global_search (bool): Search all objects globally. This overrules 'location' data.
by `location` keyword.
use_nicks (bool): Use nickname-replace (nicktype "object") on `searchdata`. use_nicks (bool): Use nickname-replace (nicktype "object") on `searchdata`.
typeclass (str or Typeclass, or list of either): Limit search only typeclass (str or Typeclass, or list of either): Limit search only
to `Objects` with this typeclass. May be a list of typeclasses to `Objects` with this typeclass. May be a list of typeclasses