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
to that of the object itself (along with normal Admin/Dev permission).
- 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)

View file

@ -941,6 +941,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
nofound_string=None,
multimatch_string=None,
use_nicks=True,
quiet=False,
**kwargs,
):
"""
@ -967,9 +968,13 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
message to echo if `searchdata` leads to multiple matches.
If not given, will fall back to the default handler.
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:
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:
Extra keywords are ignored, but are allowed in call in
order to make API more consistent with
@ -981,28 +986,33 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# handle wrapping of common terms
if searchdata.lower() in ("me", "*me", "self", "*self"):
return self
searchdata = self.nicks.nickreplace(
searchdata, categories=("account",), include_account=False
)
if search_object:
matches = ObjectDB.objects.object_search(
searchdata, typeclass=typeclass, use_nicks=use_nicks
searchdata, typeclass=typeclass
)
else:
searchdata = self.nicks.nickreplace(
searchdata, categories=("account",), include_account=False
)
matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass)
matches = _AT_SEARCH_RESULT(
matches,
self,
query=searchdata,
nofound_string=nofound_string,
multimatch_string=multimatch_string,
)
if matches and return_puppet:
try:
return matches.puppet
except AttributeError:
return None
if quiet:
matches = list(matches)
if return_puppet:
matches = [match.puppet for match in matches]
else:
matches = _AT_SEARCH_RESULT(
matches,
self,
query=searchdata,
nofound_string=nofound_string,
multimatch_string=multimatch_string,
)
if matches and return_puppet:
try:
matches = matches.puppet
except AttributeError:
return None
return matches
def access(

View file

@ -301,27 +301,60 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
session = self.session
new_character = None
character_candidates = []
if not self.args:
new_character = account.db._last_puppet
if not new_character:
character_candidates = [account.db._last_puppet] or []
if not character_candidates:
self.msg("Usage: ic <character>")
return
if not new_character:
# search for a matching character
new_character = [
char for char in search.object_search(self.args) if char.access(account, "puppet")
]
if not new_character:
self.msg("That is not a valid character choice.")
return
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)
else:
# argument given
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)
)
return
else:
new_character = new_character[0]
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 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:
account.puppet_object(session, 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)
def test_ic(self):
self.account.db._playable_characters = [self.char1]
self.account.unpuppet_object(self.session)
self.call(
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):
self.call(
account.CmdPassword(),

View file

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