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 dd5c6274b7
5 changed files with 96 additions and 36 deletions

View file

@ -68,6 +68,8 @@ without arguments starts a full interactive Python console.
- Make `Object/Room/Exit.create`'s `account` argument optional. If not given, will set perms - Make `Object/Room/Exit.create`'s `account` argument optional. If not given, will set perms
to that of the object itself (along with normal Admin/Dev permission). to that of the object itself (along with normal Admin/Dev permission).
- Make `INLINEFUNC_STACK_MAXSIZE` default visible in `settings_default.py`. - Make `INLINEFUNC_STACK_MAXSIZE` default visible in `settings_default.py`.
- Change how `ic` finds puppets; non-priveleged users will use `_playable_characters` list as
candidates, Builders+ will use list, local search and only global search if no match found.
## Evennia 0.9 (2018-2019) ## Evennia 0.9 (2018-2019)

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,16 +986,21 @@ 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
if search_object:
matches = ObjectDB.objects.object_search(
searchdata, typeclass=typeclass, use_nicks=use_nicks
)
else:
searchdata = self.nicks.nickreplace( searchdata = self.nicks.nickreplace(
searchdata, categories=("account",), include_account=False searchdata, categories=("account",), include_account=False
) )
if search_object:
matches = ObjectDB.objects.object_search(
searchdata, typeclass=typeclass
)
else:
matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass) matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass)
if quiet:
matches = list(matches)
if return_puppet:
matches = [match.puppet for match in matches]
else:
matches = _AT_SEARCH_RESULT( matches = _AT_SEARCH_RESULT(
matches, matches,
self, self,
@ -1000,7 +1010,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
) )
if matches and return_puppet: if matches and return_puppet:
try: try:
return matches.puppet matches = matches.puppet
except AttributeError: except AttributeError:
return None return None
return matches return matches

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
character_candidates.extend(
account.search(self.args, candidates=account.db._playable_characters,
search_object=True, quiet=True)
)
if account.locks.check_lockstring(account, "perm(Builder)"):
# 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 new_character: 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.") self.msg("That is not a valid character choice.")
return return
if len(new_character) > 1: if len(character_candidates) > 1:
self.msg( self.msg(
"Multiple targets with the same name:\n %s" "Multiple targets with the same name:\n %s"
% ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character) % ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in character_candidates)
) )
return return
else: else:
new_character = new_character[0] 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