11. Searching for things¶
We have gone through how to create the various entities in Evennia. But creating something is of little use if we cannot find and use it afterwards.
11.1. Main search functions¶
The base tools are the evennia.search_*
functions, such as evennia.search_object
.
import evennia
roses = evennia.search_object(key="rose")
accts = evennia.search_account(key="MyAccountName", email="foo@bar.com")
Strings are always case-insensitive, so searching for "rose"
, "Rose"
or "rOsE"
give the same results. It’s important to remember that what is returned from these search methods is a listing of zero, one or more elements - all the matches to your search. To get the first match:
rose = roses[0]
Often you really want all matches to the search parameters you specify. In other situations, having zero or more than one match is a sign of a problem and you need to handle this case yourself.
the_one_ring = evennia.search_object(key="The one Ring")
if not the_one_ring:
# handle not finding the ring at all
elif len(the_one_ring) > 1:
# handle finding more than one ring
else:
# ok - exactly one ring found
the_one_ring = the_one_ring[0]
There are equivalent search functions for all the main resources. You can find a listing of them in the Search functions section of the API frontpage.
11.2. Searching using Object.search¶
On the DefaultObject
is a .search
method which we have already tried out when we made Commands. For this to be used you must already have an object available:
rose = obj.search("rose")
This searches for objects based on key
or aliases. The .search
method wraps evennia.search_object
and handles its output in various ways.
By default it will always search for objects among those in
obj.location.contents
andobj.contents
(that is, things in obj’s inventory or in the same room).It will always return exactly one match. If it found zero or more than one match, the return is
None
.On a no-match or multimatch,
.search
will automatically send an error message toobj
.
So this method handles error messaging for you. A very common way to use it is in commands:
from evennia import Command
class CmdQuickFind(Command):
"""
Find an item in your current location.
Usage:
quickfind <query>
"""
key = "quickfind"
def func(self):
query = self.args
result = self.caller.search(query)
if not result
return
self.caller.msg(f"Found match for {query}: {foo}")
Remember, self.caller
is the one calling the command. This is usually a Character, which
inherits from DefaultObject
!
This simple little Command takes its arguments and searches for a match. If it can’t find it, result
will be None
. The error has already been reported to self.caller
so we just abort with return
.
With the global_search
flag, you can use .search
to find anything, not just stuff in the same room:
volcano = self.caller.search("Vesuvio", global_search=True)
You can limit your matches to particular typeclasses:
water_glass = self.caller.search("glass", typeclass="typeclasses.objects.WaterGlass")
If you only want to search for a specific list of things, you can do so too:
stone = self.caller.search("MyStone", candidates=[obj1, obj2, obj3, obj4])
This will only return a match if “MyStone” is in the room (or in your inventory) and is one of the four provided candidate objects. This is quite powerful, here’s how you’d find something only in your inventory:
potion = self.caller.search("Healing potion", candidates=self.caller.contents)
You can also turn off the automatic error handling:
swords = self.caller.search("Sword", quiet=True)
With quiet=True
the user will not be notified on zero or multi-match errors. Instead you are expected to handle this yourself. Furthermore, what is returned is now a list of zero, one or more matches!
11.3. What can be searched for¶
These are the main database entities one can search for:
Messages (used by
page
command by default)Help Entries (help entries created manually)
Most of the time you’ll likely spend your time searching for Objects and the occasional Accounts.
So to find an entity, what can be searched for?
11.3.1. Search by key¶
The key
is the name of the entity. Searching for this is always case-insensitive.
11.3.2. Search by aliases¶
Objects and Accounts can have any number of aliases. When searching for key
these will searched too, you can’t easily search only for aliases.
rose.aliases.add("flower")
If the above rose
has a key
"Rose"
, it can now also be found by searching for flower
. In-game
you can assign new aliases to things with the alias
command.
11.3.3. Search by location¶
Only Objects (things inheriting from evennia.DefaultObject
) has a location. The location is usually a room. The Object.search
method will automatically limit it search by location, but it also works for the general search function. If we assume room
is a particular Room instance,
chest = evennia.search_object("Treasure chest", location=room)
11.3.5. Search by Attribute¶
We can also search by the Attributes associated with entities.
For example, let’s give our rose thorns:
rose.db.has_thorns = True
wines.db.has_thorns = True
daffodil.db.has_thorns = False
Now we can find things attribute and the value we want it to have:
is_ouch = evennia.search_object_attribute("has_thorns", True)
This returns the rose and the wines.
Searching by Attribute can be very practical. But if you plan to do a search very often, searching by-tag is generally faster.
11.3.6. Search by Typeclass¶
Sometimes it’s useful to find all objects of a specific Typeclass. All of Evennia’s search tools support this.
all_roses = evennia.search_object(typeclass="typeclasses.flowers.Rose")
If you have the Rose
class already imported you can also pass it directly:
all_roses = evennia.search_object(typeclass=Rose)
You can also search using the typeclass itself:
all_roses = Rose.objects.all()
This last way of searching is a simple form of a Django query. This is a way to express SQL queries using Python. See the next lesson, where we’ll explore this way to searching in more detail.
11.3.7. Search by dbref¶
The database id or #dbref
is unique and never-reused within each database table. In search methods you can replace the search for key
with the dbref to search for. This must be written as a string #dbref
:
the_answer = self.caller.search("#42")
eightball = evennia.search_object("#8")
Since #dbref
is always unique, this search is always global.
Warning
Relying on #dbrefs
In legacy code bases you may be used to relying a lot on #dbrefs to find and track things. Looking something up by #dbref can be practical - if used occationally. It is however considered bad practice to rely on hard-coded #dbrefs in Evennia. Especially to expect end users to know them. It makes your code fragile and hard to maintain, while tying your code to the exact layout of the database. In 99% of use cases you should organize your code such that you pass the actual objects around and search by key/tags/attribute instead.
11.4. Finding objects relative each other¶
It’s important to understand how objects relate to one another when searching.
Let’s consider a chest
with a coin
inside it. The chests stand in a room dungeon
. In the dungeon is also a door
. This is an exit leading outside.
┌───────────────────────┐
│dungeon │
│ ┌─────────┐ │
│ │chest │ ┌────┐ │
│ │ ┌────┐ │ │door│ │
│ │ │coin│ │ └────┘ │
│ │ └────┘ │ │
│ │ │ │
│ └─────────┘ │
│ │
└───────────────────────┘
coin.location
ischest
.chest.location
isdungeon
.door.location
isdungeon
.room.location
isNone
since it’s not inside something else.
One can use this to find what is inside what. For example, coin.location.location
is the room
.
We can also find what is inside each object. This is a list of things.
room.contents
is[chest, door]
chest.contents
is[coin]
coin.contents
is[]
, the empty list since there’s nothing ‘inside’ the coin.door.contents
is[]
too.
A convenient helper is .contents_get
- this allows to restrict what is returned:
room.contents_get(exclude=chest)
- this returns everything in the room except the chest (maybe it’s hidden?)
There is a special property for finding exits:
room.exits
is[door]
coin.exits
is[]
(same for all the other objects)
There is a property .destination
which is only used by exits:
door.destination
isoutside
(or wherever the door leads)room.destination
isNone
(same for all the other non-exit objects)
You can also include this information in searches:
from evennia import search_object
# we assume only one match of each
dungeons = search_object("dungeon", typeclass="typeclasses.rooms.Room")
chests = search_object("chest", location=dungeons[0])
# find if there are any skulls in the chest
skulls = search_object("Skull", candidates=chests[0].contents)
More advanced, nested queries like this can however often be made more efficient by using the hints in the next lesson.
11.5. Summary¶
Knowing how to find things is important and the tools from this section will serve you well. These tools will cover most of your needs …
… but not always. In the next lesson we will dive further into more complex searching when we look at Django queries and querysets in earnest.