mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Made a lot of progress on the search tutorial
This commit is contained in:
parent
3f1a3612e4
commit
c54625b305
15 changed files with 786 additions and 636 deletions
|
|
@ -52,7 +52,7 @@ using such a checker can be a good start to weed out the simple problems.
|
|||
|
||||
### Plan before you code
|
||||
|
||||
Before you start coding away at your dream game, take a look at our [Game Planning](../Howto/Starting/Game-Planning)
|
||||
Before you start coding away at your dream game, take a look at our [Game Planning](../Howto/Starting/Part2/Game-Planning)
|
||||
page. It might hopefully help you avoid some common pitfalls and time sinks.
|
||||
|
||||
### Code in your game folder, not in the evennia/ repository
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ chat](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9
|
|||
on IRC. This allows you to chat directly with other developers new and old as well as with the devs
|
||||
of Evennia itself. This chat is logged (you can find links on http://www.evennia.com) and can also
|
||||
be searched from the same place for discussion topics you are interested in.
|
||||
2. Read the [Game Planning](Howto/Starting/Game-Planning) wiki page. It gives some ideas for your work flow and the
|
||||
2. Read the [Game Planning](Howto/Starting/Part2/Game-Planning) wiki page. It gives some ideas for your work flow and the
|
||||
state of mind you should aim for - including cutting down the scope of your game for its first
|
||||
release.
|
||||
3. Do the [Tutorial for basic MUSH-like game](Howto/Starting/Tutorial-for-basic-MUSH-like-game) carefully from
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ in mind for your own game, this will give you a good start.
|
|||
### Part 2: What we want
|
||||
|
||||
1. [Introduction & Overview](Starting/Starting-Part2)
|
||||
1. [On planning a game](Starting/Game-Planning)
|
||||
1. [On planning a game](Starting/Part2/Game-Planning)
|
||||
1. [Multisession modes](Multi-session-modes)
|
||||
1. [Layout of our tutorial game](Game-Tutorial-Planning)
|
||||
1. [Making use of contribs](Using-Contribs)
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
## Evennia API overview
|
||||
|
||||
If you cloned the GIT repo following the instructions, you will have a folder named `evennia`. The
|
||||
top level of it contains Python package specific stuff such as a readme file, `setup.py` etc. It
|
||||
also has two subfolders`bin/` and `evennia/` (again).
|
||||
|
||||
The `bin/` directory holds OS-specific binaries that will be used when installing Evennia with `pip`
|
||||
as per the [Getting started](../Setup/Getting-Started) instructions. The library itself is in the `evennia`
|
||||
subfolder. From your code you will access this subfolder simply by `import evennia`.
|
||||
|
||||
- evennia
|
||||
- [`__init__.py`](Evennia-API) - The "flat API" of Evennia resides here.
|
||||
- [`commands/`](Commands) - The command parser and handler.
|
||||
- `default/` - The [default commands](../../Component/Default-Command-Help) and cmdsets.
|
||||
- [`comms/`](Communications) - Systems for communicating in-game.
|
||||
- `contrib/` - Optional plugins too game-specific for core Evennia.
|
||||
- `game_template/` - Copied to become the "game directory" when using `evennia --init`.
|
||||
- [`help/`](Help-System) - Handles the storage and creation of help entries.
|
||||
- `locale/` - Language files ([i18n](../../Concept/Internationalization)).
|
||||
- [`locks/`](Locks) - Lock system for restricting access to in-game entities.
|
||||
- [`objects/`](Objects) - In-game entities (all types of items and Characters).
|
||||
- [`prototypes/`](Spawner-and-Prototypes) - Object Prototype/spawning system and OLC menu
|
||||
- [`accounts/`](Accounts) - Out-of-game Session-controlled entities (accounts, bots etc)
|
||||
- [`scripts/`](Scripts) - Out-of-game entities equivalence to Objects, also with timer support.
|
||||
- [`server/`](Portal-And-Server) - Core server code and Session handling.
|
||||
- `portal/` - Portal proxy and connection protocols.
|
||||
- [`settings_default.py`](Server-Conf#Settings-file) - Root settings of Evennia. Copy settings
|
||||
from here to `mygame/server/settings.py` file.
|
||||
- [`typeclasses/`](Typeclasses) - Abstract classes for the typeclass storage and database system.
|
||||
- [`utils/`](Coding-Utils) - Various miscellaneous useful coding resources.
|
||||
- [`web/`](Web-Features) - Web resources and webserver. Partly copied into game directory on
|
||||
initialization.
|
||||
|
||||
All directories contain files ending in `.py`. These are Python *modules* and are the basic units of
|
||||
Python code. The roots of directories also have (usually empty) files named `__init__.py`. These are
|
||||
required by Python so as to be able to find and import modules in other directories. When you have
|
||||
run Evennia at least once you will find that there will also be `.pyc` files appearing, these are
|
||||
pre-compiled binary versions of the `.py` files to speed up execution.
|
||||
|
||||
The root of the `evennia` folder has an `__init__.py` file containing the "[flat API](../../Evennia-API)".
|
||||
This holds shortcuts to various subfolders in the evennia library. It is provided to make it easier
|
||||
to find things; it allows you to just import `evennia` and access things from that rather than
|
||||
having to import from their actual locations inside the source tree.
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Our own commands
|
||||
|
||||
[prev lesson](Python-classes-and-objects) | [next lesson](More-on-Commands)
|
||||
[prev lesson](Searching-Things) | [next lesson]()
|
||||
|
||||
In this lesson we'll learn how to create our own Evennia _Commands_. If you are new to Python you'll
|
||||
also learn some more basics about how to manipulate strings and get information out of Evennia.
|
||||
|
|
@ -393,5 +393,4 @@ We also upset a dragon.
|
|||
In the next lesson we'll learn how to hit Smaug with different weapons. We'll also
|
||||
get into how we replace and extend Evennia's default Commands.
|
||||
|
||||
|
||||
[prev lesson](Python-classes-and-objects) | [next lesson](More-on-Commands)
|
||||
[prev lesson](Searching-Things) | [next lesson]()
|
||||
|
|
|
|||
53
docs/source/Howto/Starting/Part1/Creating-Things.md
Normal file
53
docs/source/Howto/Starting/Part1/Creating-Things.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Creating things
|
||||
|
||||
[prev lesson](Learning-Typeclasses) | [next lesson](Searching-Things)
|
||||
|
||||
We have already created some things - dragons for example. There are many different things to create
|
||||
in Evennia though. In the last lesson we learned about typeclasses, the way to make objects persistent in the database.
|
||||
|
||||
Given the path to a Typeclass, there are three ways to create an instance of it:
|
||||
|
||||
- Firstly, you can call the class directly, and then `.save()` it:
|
||||
|
||||
obj = SomeTypeClass(db_key=...)
|
||||
obj.save()
|
||||
|
||||
This has the drawback of being two operations; you must also import the class and have to pass
|
||||
the actual database field names, such as `db_key` instead of `key` as keyword arguments.
|
||||
- Secondly you can use the Evennia creation helpers:
|
||||
|
||||
obj = evennia.create_object(SomeTypeClass, key=...)
|
||||
|
||||
This is the recommended way if you are trying to create things in Python. The first argument can either be
|
||||
the class _or_ the python-path to the typeclass, like `"path.to.SomeTypeClass"`. It can also be `None` in which
|
||||
case the Evennia default will be used. While all the creation methods
|
||||
are available on `evennia`, they are actually implemented in [evennia/utils/create.py](api:evennia.utils.create).
|
||||
- Finally, you can create objects using an in-game command, such as
|
||||
|
||||
create/drop obj:path.to.SomeTypeClass
|
||||
|
||||
As a developer you are usually best off using the two other methods, but a command is usually the only way
|
||||
to let regular players or builders without Python-access help build the game world.
|
||||
|
||||
## Creating Objects
|
||||
|
||||
This is one of the most common creation-types. These are entities that inherits from `DefaultObject` at any distance.
|
||||
They have an existence in the game world and includes rooms, characters, exits, weapons, flower pots and castles.
|
||||
|
||||
> py
|
||||
> import evennia
|
||||
> rose = evennia.create_object(key="rose")
|
||||
|
||||
Since we didn't specify the `typeclass` as the first argument, the default given by `settings.BASE_OBJECT_TYPECLASS`
|
||||
(`typeclasses.objects.Object`) will be used.
|
||||
|
||||
## Creating Accounts
|
||||
|
||||
An _Account_ is an out-of-character (OOC) entity, with no existence in the game world.
|
||||
You can find the parent class for Accounts in `typeclasses/accounts.py`.
|
||||
|
||||
_TODO_
|
||||
|
||||
|
||||
[prev lesson](Learning-Typeclasses) | [next lesson](Searching-Things)
|
||||
|
||||
|
|
@ -1,562 +0,0 @@
|
|||
# Overview of the Evennia API
|
||||
|
||||
In the last few lessons we have explored the gamedir, learned about typeclasses and commands. In the process
|
||||
we have used several resources from the Evennia library. Some examples:
|
||||
|
||||
- `evennia.DefaultObject`, `evennia.DefaultCharacter` and (inherited) methods on these classes like `.msg`
|
||||
and `at_object_create` but also `.cmdset.add` for adding new cmdsets.
|
||||
- `evennia.search_object` for finding lists of objects anywhere.
|
||||
- `evennia.create_object` for creating objects in code instead of using the in-game `create` command.
|
||||
- `evennia.Command` with methods like `func` and `parse` to implement new commands
|
||||
- `evennia.CmdSet` for storing commands
|
||||
- `evennia.default_cmds` holding references to all default Command classes like `look`, `dig` and so on.
|
||||
|
||||
Evennia has a lot of resources to help you make your game. We have just given a selection of them for you to try
|
||||
out so far (and we'll show off many more in the lessons to come). Now we'll teach you how to find them
|
||||
for yourself.
|
||||
|
||||
## Exploring the API
|
||||
|
||||
The Evennia _API_
|
||||
([Application Programming Interface](https://en.wikipedia.org/wiki/Application_programming_interface)) is what
|
||||
you use to access things inside the `evennia` package.
|
||||
|
||||
Open the [API frontpage](../../../Evennia-API). This page sums up the main components of Evennia with a short
|
||||
description of each. Try clicking through to a few entries - once you get deep enough you'll see full descriptions
|
||||
of each component along with their documentation. You can also click `[source]` to see the full Python source
|
||||
for each thing.
|
||||
|
||||
### Browsing the code
|
||||
|
||||
You can browse [the evennia repository on github](https://github.com/evennia/evennia). This is exactly
|
||||
what you can download from us. The github repo is also searchable.
|
||||
|
||||
You can also clone the evennia repo to your own computer and read the sources locally. This is necessary
|
||||
if you want to help with Evennia's development itself. See the
|
||||
[extended install instructions](../../../Setup/Extended-Installation) if you want to do this. The short of is to install `git` and run
|
||||
|
||||
git clone https://github.com/evennia/evennia.git
|
||||
|
||||
In the terminal/console you can search for anything using `git` (make sure you are inside the repo):
|
||||
|
||||
git grep -n "class DefaultObject"
|
||||
|
||||
will quickly tell you which file the DefaultObject class is defined and on which line.
|
||||
|
||||
If you read the code on `github` or cloned the repo yourself, you will find this being the outermost folder
|
||||
structure:
|
||||
|
||||
evennia/
|
||||
bin/
|
||||
CHANGELOG.md
|
||||
...
|
||||
...
|
||||
docs/
|
||||
evennia/
|
||||
|
||||
That internal folder `evennia/evennia/` is the actual library, the thing covered by the API auto-docs and
|
||||
what you get when you do `import evennia`. The outermost level is part of the Evennia package distribution and
|
||||
installation. It's not something we'll bother with for this tutorial.
|
||||
|
||||
> The `evennia/docs/` folder contains, well, this documentation. See [contributing to the docs](../../../Contributing-Docs) if you
|
||||
want to learn more about how this works.
|
||||
|
||||
## Overview of the `evennia` root package
|
||||
|
||||
```
|
||||
evennia/
|
||||
__init__.py
|
||||
settings_default.py
|
||||
accounts/
|
||||
commands/
|
||||
comms/
|
||||
game_template/
|
||||
help/
|
||||
locale/
|
||||
locks/
|
||||
objects/
|
||||
prototypes/
|
||||
scripts/
|
||||
server/
|
||||
typeclasses/
|
||||
utils/
|
||||
web/
|
||||
```
|
||||
|
||||
```sidebar:: __init__.py
|
||||
|
||||
The `__init__.py` file is a special Python filename used to represent a Python 'package'.
|
||||
When you import `evennia` on its own, you import this file. When you do `evennia.foo` Python will
|
||||
first look for a property `.foo` in `__init__.py` and then for a module or folder in the same
|
||||
location.
|
||||
|
||||
```
|
||||
While all the actual Evennia code is found in the various folders, the `__init__.py` contains "shortcuts"
|
||||
to useful things you will often need. This allows you to do things like `from evennia import DefaultObject`
|
||||
even though the `DefaultObject` is not actually defined there. Let's see how that works:
|
||||
|
||||
[Look at Line 189](evennia/__init__.py#L189) of `evennia/__init__.py` and you'll find this line:
|
||||
|
||||
```python
|
||||
# ...
|
||||
from .objects.objects import DefaultObject
|
||||
# ...
|
||||
```
|
||||
|
||||
```sidebar:: Relative and absolute imports
|
||||
|
||||
The first full-stop in `from .objects.objects ...` means that
|
||||
we are importing from the current location. This is called a `relative import`.
|
||||
By comparison, `from evennia.objects.objects` is an `absolute import`. In this particular
|
||||
case, the two would give the same result.
|
||||
```
|
||||
|
||||
Since `DefaultObject` is imported into `__init__.py`, it means we can do
|
||||
`from evennia import DefaultObject` even though the code for it is not actually here.
|
||||
|
||||
So if we want to find the code for `DefaultObject` we need to look in
|
||||
`evennia/objects/objects.py`. Here's how to look it up in these docs:
|
||||
|
||||
1. Open the [API frontpage](../../../Evennia-API)
|
||||
2. Locate the link to [evennia.objects](api:evennia.objects) and click on it.
|
||||
3. Click through to [evennia.objects.objects](api:evennia.objects.objects).
|
||||
4. You are now in the python module. Scroll down (or search in your web browser) to find the `DefaultObject` class.
|
||||
5. You can now read what this does and what methods are on it. If you want to see the full source, click the
|
||||
\[[source](src:evennia.objects.objects#DefaultObject)\] link.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Tutorial Searching For Objects
|
||||
|
||||
You will often want to operate on a specific object in the database. For example when a player
|
||||
attacks a named target you'll need to find that target so it can be attacked. Or when a rain storm
|
||||
draws in you need to find all outdoor-rooms so you can show it raining in them. This tutorial
|
||||
explains Evennia's tools for searching.
|
||||
|
||||
## Things to search for
|
||||
|
||||
The first thing to consider is the base type of the thing you are searching for. Evennia organizes
|
||||
its database into a few main tables: [Objects](../../../Component/Objects), [Accounts](../../../Component/Accounts), [Scripts](../../../Component/Scripts),
|
||||
[Channels](../../../Component/Communications#channels), [Messages](Communication#Msg) and [Help Entries](../../../Component/Help-System).
|
||||
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?
|
||||
|
||||
- The `key` is the name of the entity. While you can get this from `obj.key` the *database field*
|
||||
is actually named `obj.db_key` - this is useful to know only when you do [direct database
|
||||
queries](Tutorial-Searching-For-Objects#queries-in-django). The one exception is `Accounts`, where
|
||||
the database field for `.key` is instead named `username` (this is a Django requirement). When you
|
||||
don't specify search-type, you'll usually search based on key. *Aliases* are extra names given to
|
||||
Objects using something like `@alias` or `obj.aliases.add('name')`. The main search functions (see
|
||||
below) will automatically search for aliases whenever you search by-key.
|
||||
- [Tags](../../../Component/Tags) are the main way to group and identify objects in Evennia. Tags can most often be
|
||||
used (sometimes together with keys) to uniquely identify an object. For example, even though you
|
||||
have two locations with the same name, you can separate them by their tagging (this is how Evennia
|
||||
implements 'zones' seen in other systems). Tags can also have categories, to further organize your
|
||||
data for quick lookups.
|
||||
- An object's [Attributes](../../../Component/Attributes) can also used to find an object. This can be very useful but
|
||||
since Attributes can store almost any data they are far less optimized to search for than Tags or
|
||||
keys.
|
||||
- The object's [Typeclass](../../../Component/Typeclasses) indicate the sub-type of entity. A Character, Flower or
|
||||
Sword are all types of Objects. A Bot is a kind of Account. The database field is called
|
||||
`typeclass_path` and holds the full Python-path to the class. You can usually specify the
|
||||
`typeclass` as an argument to Evennia's search functions as well as use the class directly to limit
|
||||
queries.
|
||||
- The `location` is only relevant for [Objects](../../../Component/Objects) but is a very common way to weed down the
|
||||
number of candidates before starting to search. The reason is that most in-game commands tend to
|
||||
operate on things nearby (in the same room) so the choices can be limited from the start.
|
||||
- The database id or the '#dbref' is unique (and never re-used) within each database table. So while
|
||||
there is one and only one Object with dbref `#42` there could also be an Account or Script with the
|
||||
dbref `#42` at the same time. In almost all search methods you can replace the "key" search
|
||||
criterion with `"#dbref"` to search for that id. This can occasionally be practical and may be what
|
||||
you are used to from other code bases. But it is considered *bad practice* in Evennia to rely on
|
||||
hard-coded #dbrefs to do your searches. It makes your code tied to the exact layout of the database.
|
||||
It's also not very maintainable to have to remember abstract numbers. Passing the actual objects
|
||||
around and searching by Tags and/or keys will usually get you what you need.
|
||||
|
||||
|
||||
## Getting objects inside another
|
||||
|
||||
All in-game [Objects](../../../Component/Objects) have a `.contents` property that returns all objects 'inside' them
|
||||
(that is, all objects which has its `.location` property set to that object. This is a simple way to
|
||||
get everything in a room and is also faster since this lookup is cached and won't hit the database.
|
||||
|
||||
- `roomobj.contents` returns a list of all objects inside `roomobj`.
|
||||
- `obj.contents` same as for a room, except this usually represents the object's inventory
|
||||
- `obj.location.contents` gets everything in `obj`'s location (including `obj` itself).
|
||||
- `roomobj.exits` returns all exits starting from `roomobj` (Exits are here defined as Objects with
|
||||
their `destination` field set).
|
||||
- `obj.location.contents_get(exclude=obj)` - this helper method returns all objects in `obj`'s
|
||||
location except `obj`.
|
||||
|
||||
## Searching using `Object.search`
|
||||
|
||||
Say you have a [command](../../../Component/Commands), and you want it to do something to a target. You might be
|
||||
wondering how you retrieve that target in code, and that's where Evennia's search utilities come in.
|
||||
In the most common case, you'll often use the `search` method of the `Object` or `Account`
|
||||
typeclasses. In a command, the `.caller` property will refer back to the object using the command
|
||||
(usually a `Character`, which is a type of `Object`) while `.args` will contain Command's arguments:
|
||||
|
||||
```python
|
||||
# e.g. in file mygame/commands/command.py
|
||||
|
||||
from evennia import default_cmds
|
||||
|
||||
class CmdPoke(default_cmds.MuxCommand):
|
||||
"""
|
||||
Pokes someone.
|
||||
|
||||
Usage: poke <target>
|
||||
"""
|
||||
key = "poke"
|
||||
|
||||
def func(self):
|
||||
"""Executes poke command"""
|
||||
target = self.caller.search(self.args)
|
||||
if not target:
|
||||
# we didn't find anyone, but search has already let the
|
||||
# caller know. We'll just return, since we're done
|
||||
return
|
||||
# we found a target! we'll do stuff to them.
|
||||
target.msg("You have been poked by %s." % self.caller)
|
||||
self.caller.msg("You have poked %s." % target)
|
||||
```
|
||||
By default, the search method of a Character will attempt to find a unique object match for the
|
||||
string sent to it (`self.args`, in this case, which is the arguments passed to the command by the
|
||||
player) in the surroundings of the Character - the room or their inventory. If there is no match
|
||||
found, the return value (which is assigned to `target`) will be `None`, and an appropriate failure
|
||||
message will be sent to the Character. If there's not a unique match, `None` will again be returned,
|
||||
and a different error message will be sent asking them to disambiguate the multi-match. By default,
|
||||
the user can then pick out a specific match using with a number and dash preceding the name of the
|
||||
object: `character.search("2-pink unicorn")` will try to find the second pink unicorn in the room.
|
||||
|
||||
The search method has many [arguments](github:evennia.objects.objects#defaultcharactersearch) that
|
||||
allow you to refine the search, such as by designating the location to search in or only matching
|
||||
specific typeclasses.
|
||||
|
||||
## Searching using `utils.search`
|
||||
|
||||
Sometimes you will want to find something that isn't tied to the search methods of a character or
|
||||
account. In these cases, Evennia provides a [utility module with a number of search
|
||||
functions](github:evennia.utils.search). For example, suppose you want a command that will find and
|
||||
display all the rooms that are tagged as a 'hangout', for people to gather by. Here's a simple
|
||||
Command to do this:
|
||||
|
||||
```python
|
||||
# e.g. in file mygame/commands/command.py
|
||||
|
||||
from evennia import default_cmds
|
||||
from evennia.utils.search import search_tag
|
||||
|
||||
class CmdListHangouts(default_cmds.MuxCommand):
|
||||
"""Lists hangouts"""
|
||||
key = "hangouts"
|
||||
|
||||
def func(self):
|
||||
"""Executes 'hangouts' command"""
|
||||
hangouts = search_tag(key="hangout",
|
||||
category="location tags")
|
||||
self.caller.msg("Hangouts available: {}".format(
|
||||
", ".join(str(ob) for ob in hangouts)))
|
||||
```
|
||||
|
||||
This uses the `search_tag` function to find all objects previously tagged with [Tags](../../../Component/Tags)
|
||||
"hangout" and with category "location tags".
|
||||
|
||||
Other important search methods in `utils.search` are
|
||||
|
||||
- `search_object`
|
||||
- `search_account`
|
||||
- `search_scripts`
|
||||
- `search_channel`
|
||||
- `search_message`
|
||||
- `search_help`
|
||||
- `search_tag` - find Objects with a given Tag.
|
||||
- `search_account_tag` - find Accounts with a given Tag.
|
||||
- `search_script_tag` - find Scripts with a given Tag.
|
||||
- `search_channel_tag` - find Channels with a given Tag.
|
||||
- `search_object_attribute` - find Objects with a given Attribute.
|
||||
- `search_account_attribute` - find Accounts with a given Attribute.
|
||||
- `search_attribute_object` - this returns the actual Attribute, not the object it sits on.
|
||||
|
||||
> Note: All search functions return a Django `queryset` which is technically a list-like
|
||||
representation of the database-query it's about to do. Only when you convert it to a real list, loop
|
||||
over it or try to slice or access any of its contents will the datbase-lookup happen. This means you
|
||||
could yourself customize the query further if you know what you are doing (see the next section).
|
||||
|
||||
## Queries in Django
|
||||
|
||||
*This is an advanced topic.*
|
||||
|
||||
Evennia's search methods should be sufficient for the vast majority of situations. But eventually
|
||||
you might find yourself trying to figure out how to get searches for unusual circumstances: Maybe
|
||||
you want to find all characters who are *not* in rooms tagged as hangouts *and* have the lycanthrope
|
||||
tag *and* whose names start with a vowel, but *not* with 'Ab', and *only if* they have 3 or more
|
||||
objects in their inventory ... You could in principle use one of the earlier search methods to find
|
||||
all candidates and then loop over them with a lot of if statements in raw Python. But you can do
|
||||
this much more efficiently by querying the database directly.
|
||||
|
||||
Enter [django's querysets](https://docs.djangoproject.com/en/1.11/ref/models/querysets/). A QuerySet
|
||||
is the representation of a database query and can be modified as desired. Only once one tries to
|
||||
retrieve the data of that query is it *evaluated* and does an actual database request. This is
|
||||
useful because it means you can modify a query as much as you want (even pass it around) and only
|
||||
hit the database once you are happy with it.
|
||||
Evennia's search functions are themselves an even higher level wrapper around Django's queries, and
|
||||
many search methods return querysets. That means that you could get the result from a search
|
||||
function and modify the resulting query to your own ends to further tweak what you search for.
|
||||
|
||||
Evaluated querysets can either contain objects such as Character objects, or lists of values derived
|
||||
from the objects. Queries usually use the 'manager' object of a class, which by convention is the
|
||||
`.objects` attribute of a class. For example, a query of Accounts that contain the letter 'a' could
|
||||
be:
|
||||
|
||||
```python
|
||||
from typeclasses.accounts import Account
|
||||
|
||||
queryset = Account.objects.filter(username__contains='a')
|
||||
|
||||
```
|
||||
|
||||
The `filter` method of a manager takes arguments that allow you to define the query, and you can
|
||||
continue to refine the query by calling additional methods until you evaluate the queryset, causing
|
||||
the query to be executed and return a result. For example, if you have the result above, you could,
|
||||
without causing the queryset to be evaluated yet, get rid of matches that contain the letter 'e by
|
||||
doing this:
|
||||
|
||||
```python
|
||||
queryset = result.exclude(username__contains='e')
|
||||
|
||||
```
|
||||
|
||||
> You could also have chained `.exclude` directly to the end of the previous line.
|
||||
|
||||
Once you try to access the result, the queryset will be evaluated automatically under the hood:
|
||||
|
||||
```python
|
||||
accounts = list(queryset) # this fills list with matches
|
||||
|
||||
for account in queryset:
|
||||
# do something with account
|
||||
|
||||
accounts = queryset[:4] # get first four matches
|
||||
account = queryset[0] # get first match
|
||||
# etc
|
||||
|
||||
```
|
||||
|
||||
### Limiting by typeclass
|
||||
|
||||
Although `Character`s, `Exit`s, `Room`s, and other children of `DefaultObject` all shares the same
|
||||
underlying database table, Evennia provides a shortcut to do more specific queries only for those
|
||||
typeclasses. For example, to find only `Character`s whose names start with 'A', you might do:
|
||||
|
||||
```python
|
||||
Character.objects.filter(db_key__startswith="A")
|
||||
|
||||
```
|
||||
|
||||
If Character has a subclass `Npc` and you wanted to find only Npc's you'd instead do
|
||||
|
||||
```python
|
||||
Npc.objects.filter(db_key__startswith="A")
|
||||
|
||||
```
|
||||
|
||||
If you wanted to search both Characters and all its subclasses (like Npc) you use the `*_family`
|
||||
method which is added by Evennia:
|
||||
|
||||
|
||||
```python
|
||||
Character.objects.filter_family(db_key__startswith="A")
|
||||
```
|
||||
|
||||
The higher up in the inheritance hierarchy you go the more objects will be included in these
|
||||
searches. There is one special case, if you really want to include *everything* from a given
|
||||
database table. You do that by searching on the database model itself. These are named `ObjectDB`,
|
||||
`AccountDB`, `ScriptDB` etc.
|
||||
|
||||
```python
|
||||
from evennia import AccountDB
|
||||
|
||||
# all Accounts in the database, regardless of typeclass
|
||||
all = AccountDB.objects.all()
|
||||
|
||||
```
|
||||
|
||||
Here are the most commonly used methods to use with the `objects` managers:
|
||||
|
||||
- `filter` - query for a listing of objects based on search criteria. Gives empty queryset if none
|
||||
were found.
|
||||
- `get` - query for a single match - raises exception if none were found, or more than one was
|
||||
found.
|
||||
- `all` - get all instances of the particular type.
|
||||
- `filter_family` - like `filter`, but search all sub classes as well.
|
||||
- `get_family` - like `get`, but search all sub classes as well.
|
||||
- `all_family` - like `all`, but return entities of all subclasses as well.
|
||||
|
||||
## Multiple conditions
|
||||
|
||||
If you pass more than one keyword argument to a query method, the query becomes an `AND`
|
||||
relationship. For example, if we want to find characters whose names start with "A" *and* are also
|
||||
werewolves (have the `lycanthrope` tag), we might do:
|
||||
|
||||
```python
|
||||
queryset = Character.objects.filter(db_key__startswith="A", db_tags__db_key="lycanthrope")
|
||||
```
|
||||
|
||||
To exclude lycanthropes currently in rooms tagged as hangouts, we might tack on an `.exclude` as
|
||||
before:
|
||||
|
||||
```python
|
||||
queryset = quersyet.exclude(db_location__db_tags__db_key="hangout")
|
||||
```
|
||||
|
||||
Note the syntax of the keywords in building the queryset. For example, `db_location` is the name of
|
||||
the database field sitting on (in this case) the `Character` (Object). Double underscore `__` works
|
||||
like dot-notation in normal Python (it's used since dots are not allowed in keyword names). So the
|
||||
instruction `db_location__db_tags__db_key="hangout"` should be read as such:
|
||||
|
||||
1. "On the `Character` object ... (this comes from us building this queryset using the
|
||||
`Character.objects` manager)
|
||||
2. ... get the value of the `db_location` field ... (this references a Room object, normally)
|
||||
3. ... on that location, get the value of the `db_tags` field ... (this is a many-to-many field that
|
||||
can be treated like an object for this purpose. It references all tags on the location)
|
||||
4. ... through the `db_tag` manager, find all Tags having a field `db_key` set to the value
|
||||
"hangout"."
|
||||
|
||||
This may seem a little complex at first, but this syntax will work the same for all queries. Just
|
||||
remember that all *database-fields* in Evennia are prefaced with `db_`. So even though Evennia is
|
||||
nice enough to alias the `db_key` field so you can normally just do `char.key` to get a character's
|
||||
name, the database field is actually called `db_key` and the real name must be used for the purpose
|
||||
of building a query.
|
||||
|
||||
> Don't confuse database fields with [Attributes](../../../Component/Attributes) you set via `obj.db.attr = 'foo'` or
|
||||
`obj.attributes.add()`. Attributes are custom database entities *linked* to an object. They are not
|
||||
separate fields *on* that object like `db_key` or `db_location` are. You can get attached Attributes
|
||||
manually through the `db_attributes` many-to-many field in the same way as `db_tags` above.
|
||||
|
||||
### Complex queries
|
||||
|
||||
What if you want to have a query with with `OR` conditions or negated requirements (`NOT`)? Enter
|
||||
Django's Complex Query object,
|
||||
[Q](https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q-objects). `Q()`
|
||||
objects take a normal django keyword query as its arguments. The special thing is that these Q
|
||||
objects can then be chained together with set operations: `|` for OR, `&` for AND, and preceded with
|
||||
`~` for NOT to build a combined, complex query.
|
||||
|
||||
In our original Lycanthrope example we wanted our werewolves to have names that could start with any
|
||||
vowel except for the specific beginning "ab".
|
||||
|
||||
```python
|
||||
from django.db.models import Q
|
||||
from typeclasses.characters import Character
|
||||
|
||||
query = Q()
|
||||
for letter in ("aeiouy"):
|
||||
query |= Q(db_key__istartswith=letter)
|
||||
query &= ~Q(db_key__istartswith="ab")
|
||||
query = Character.objects.filter(query)
|
||||
|
||||
list_of_lycanthropes = list(query)
|
||||
```
|
||||
|
||||
In the above example, we construct our query our of several Q objects that each represent one part
|
||||
of the query. We iterate over the list of vowels, and add an `OR` condition to the query using `|=`
|
||||
(this is the same idea as using `+=` which may be more familiar). Each `OR` condition checks that
|
||||
the name starts with one of the valid vowels. Afterwards, we add (using `&=`) an `AND` condition
|
||||
that is negated with the `~` symbol. In other words we require that any match should *not* start
|
||||
with the string "ab". Note that we don't actually hit the database until we convert the query to a
|
||||
list at the end (we didn't need to do that either, but could just have kept the query until we
|
||||
needed to do something with the matches).
|
||||
|
||||
### Annotations and `F` objects
|
||||
|
||||
What if we wanted to filter on some condition that isn't represented easily by a field on the
|
||||
object? Maybe we want to find rooms only containing five or more objects?
|
||||
|
||||
We *could* retrieve all interesting candidates and run them through a for-loop to get and count
|
||||
their `.content` properties. We'd then just return a list of only those objects with enough
|
||||
contents. It would look something like this (note: don't actually do this!):
|
||||
|
||||
```python
|
||||
# probably not a good idea to do it this way
|
||||
|
||||
from typeclasses.rooms import Room
|
||||
|
||||
queryset = Room.objects.all() # get all Rooms
|
||||
rooms = [room for room in queryset if len(room.contents) >= 5]
|
||||
|
||||
```
|
||||
|
||||
Once the number of rooms in your game increases, this could become quite expensive. Additionally, in
|
||||
some particular contexts, like when using the web features of Evennia, you must have the result as a
|
||||
queryset in order to use it in operations, such as in Django's admin interface when creating list
|
||||
filters.
|
||||
|
||||
Enter [F objects](https://docs.djangoproject.com/en/1.11/ref/models/expressions/#f-expressions) and
|
||||
*annotations*. So-called F expressions allow you to do a query that looks at a value of each object
|
||||
in the database, while annotations allow you to calculate and attach a value to a query. So, let's
|
||||
do the same example as before directly in the database:
|
||||
|
||||
```python
|
||||
from typeclasses.rooms import Room
|
||||
from django.db.models import Count
|
||||
|
||||
room_count = Room.objects.annotate(num_objects=Count('locations_set'))
|
||||
queryset = room_count.filter(num_objects__gte=5)
|
||||
|
||||
rooms = (Room.objects.annotate(num_objects=Count('locations_set'))
|
||||
.filter(num_objects__gte=5))
|
||||
|
||||
rooms = list(rooms)
|
||||
|
||||
```
|
||||
Here we first create an annotation `num_objects` of type `Count`, which is a Django class. Note that
|
||||
use of `location_set` in that `Count`. The `*_set` is a back-reference automatically created by
|
||||
Django. In this case it allows you to find all objects that *has the current object as location*.
|
||||
Once we have those, they are counted.
|
||||
Next we filter on this annotation, using the name `num_objects` as something we can filter for. We
|
||||
use `num_objects__gte=5` which means that `num_objects` should be greater than 5. This is a little
|
||||
harder to get one's head around but much more efficient than lopping over all objects in Python.
|
||||
|
||||
What if we wanted to compare two parameters against one another in a query? For example, what if
|
||||
instead of having 5 or more objects, we only wanted objects that had a bigger inventory than they
|
||||
had tags? Here an F-object comes in handy:
|
||||
|
||||
```python
|
||||
from django.db.models import Count, F
|
||||
from typeclasses.rooms import Room
|
||||
|
||||
result = (Room.objects.annotate(num_objects=Count('locations_set'),
|
||||
num_tags=Count('db_tags'))
|
||||
.filter(num_objects__gt=F('num_tags')))
|
||||
```
|
||||
|
||||
F-objects allows for wrapping an annotated structure on the right-hand-side of the expression. It
|
||||
will be evaluated on-the-fly as needed.
|
||||
|
||||
### Grouping By and Values
|
||||
|
||||
Suppose you used tags to mark someone belonging an organization. Now you want to make a list and
|
||||
need to get the membership count of every organization all at once. That's where annotations and the
|
||||
`.values_list` queryset method come in. Values/Values Lists are an alternate way of returning a
|
||||
queryset - instead of objects, you get a list of dicts or tuples that hold selected properties from
|
||||
the the matches. It also allows you a way to 'group up' queries for returning information. For
|
||||
example, to get a display about each tag per Character and the names of the tag:
|
||||
|
||||
```python
|
||||
result = (Character.objects.filter(db_tags__db_category="organization")
|
||||
.values_list('db_tags__db_key')
|
||||
.annotate(cnt=Count('id'))
|
||||
.order_by('-cnt'))
|
||||
```
|
||||
The result queryset will be a list of tuples ordered in descending order by the number of matches,
|
||||
in a format like the following:
|
||||
```
|
||||
[('Griatch Fanclub', 3872), ("Chainsol's Ainneve Testers", 2076), ("Blaufeuer's Whitespace Fixers",
|
||||
1903),
|
||||
("Volund's Bikeshed Design Crew", 1764), ("Tehom's Misanthropes", 1)]
|
||||
129
docs/source/Howto/Starting/Part1/Evennia-Library-Overview.md
Normal file
129
docs/source/Howto/Starting/Part1/Evennia-Library-Overview.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# Overview of the Evennia library
|
||||
|
||||
[prev lesson](Python-classes-and-objects) | [next lesson](Learning-Typeclasses)
|
||||
|
||||
```sidebar:: API
|
||||
|
||||
API stands for `Application Programming Interface`, a description for how to access
|
||||
the resources of a program or library.
|
||||
```
|
||||
A good place to start exploring Evennia is the [Evenia-API frontpage](../../../Evennia-API).
|
||||
This page sums up the main components of Evennia with a short description of each. Try clicking through
|
||||
to a few entries - once you get deep enough you'll see full descriptions
|
||||
of each component along with their documentation. You can also click `[source]` to see the full Python source
|
||||
for each thing.
|
||||
|
||||
You can also browse [the evennia repository on github](https://github.com/evennia/evennia). This is exactly
|
||||
what you can download from us. The github repo is also searchable.
|
||||
|
||||
Finally, you can clone the evennia repo to your own computer and read the sources locally. This is necessary
|
||||
if you want to help with Evennia's development itself. See the
|
||||
[extended install instructions](../../../Setup/Extended-Installation) if you want to do this.
|
||||
|
||||
### Where is it?
|
||||
|
||||
If Evennia is installed, you can import from it simply with
|
||||
|
||||
import evennia
|
||||
from evennia import some_module
|
||||
from evennia.some_module.other_module import SomeClass
|
||||
|
||||
and so on.
|
||||
|
||||
If you installed Evennia with `pip install`, the library folder will be installed deep inside your Python
|
||||
installation. If you cloned the repo there will be a folder `evennia` on your hard drive there.
|
||||
|
||||
If you cloned the repo or read the code on `github` you'll find this being the outermost structure:
|
||||
|
||||
evennia/
|
||||
bin/
|
||||
CHANGELOG.md
|
||||
...
|
||||
...
|
||||
docs/
|
||||
evennia/
|
||||
|
||||
This outer layer is for Evennia's installation and package distribution. That internal folder `evennia/evennia/` is
|
||||
the _actual_ library, the thing covered by the API auto-docs and what you get when you do `import evennia`.
|
||||
|
||||
> The `evennia/docs/` folder contains the sources for this documentation. See
|
||||
> [contributing to the docs](../../../Contributing-Docs) if you want to learn more about how this works.
|
||||
|
||||
This the the structure of the Evennia library:
|
||||
|
||||
- evennia
|
||||
- [`__init__.py`](Evennia-API#shortcuts) - The "flat API" of Evennia resides here.
|
||||
- [`settings_default.py`](Server-Conf#Settings-file) - Root settings of Evennia. Copy settings
|
||||
from here to `mygame/server/settings.py` file.
|
||||
- [`commands/`](Commands) - The command parser and handler.
|
||||
- `default/` - The [default commands](../../../Component/Default-Command-Help) and cmdsets.
|
||||
- [`comms/`](Communications) - Systems for communicating in-game.
|
||||
- `contrib/` - Optional plugins too game-specific for core Evennia.
|
||||
- `game_template/` - Copied to become the "game directory" when using `evennia --init`.
|
||||
- [`help/`](Help-System) - Handles the storage and creation of help entries.
|
||||
- `locale/` - Language files ([i18n](../../../Concept/Internationalization)).
|
||||
- [`locks/`](Locks) - Lock system for restricting access to in-game entities.
|
||||
- [`objects/`](Objects) - In-game entities (all types of items and Characters).
|
||||
- [`prototypes/`](Spawner-and-Prototypes) - Object Prototype/spawning system and OLC menu
|
||||
- [`accounts/`](Accounts) - Out-of-game Session-controlled entities (accounts, bots etc)
|
||||
- [`scripts/`](Scripts) - Out-of-game entities equivalence to Objects, also with timer support.
|
||||
- [`server/`](Portal-And-Server) - Core server code and Session handling.
|
||||
- `portal/` - Portal proxy and connection protocols.
|
||||
- [`typeclasses/`](Typeclasses) - Abstract classes for the typeclass storage and database system.
|
||||
- [`utils/`](Coding-Utils) - Various miscellaneous useful coding resources.
|
||||
- [`web/`](Web-Features) - Web resources and webserver. Partly copied into game directory on initialization.
|
||||
|
||||
```sidebar:: __init__.py
|
||||
|
||||
The `__init__.py` file is a special Python filename used to represent a Python 'package'.
|
||||
When you import `evennia` on its own, you import this file. When you do `evennia.foo` Python will
|
||||
first look for a property `.foo` in `__init__.py` and then for a module or folder of that name
|
||||
in the same location.
|
||||
|
||||
```
|
||||
|
||||
While all the actual Evennia code is found in the various folders, the `__init__.py` represents the entire
|
||||
package `evennia`. It contains "shortcuts" to code that is actually located elsewhere. Most of these shortcuts
|
||||
are listed if you [scroll down a bit](../../../Evennia-API) on the Evennia-API page.
|
||||
|
||||
## An example of exploring the library
|
||||
|
||||
In the previous lesson we took a brief look at `mygame/typeclasses/objects` as an example of a Python module. Let's
|
||||
open it again. Inside is the `Object` class, which inherits from `DefaultObject`.
|
||||
Near the top of the module is this line:
|
||||
|
||||
from evennia import DefaultObject
|
||||
|
||||
We want to figure out just what this DefaultObject offers. Since this is imported directly from `evennia`, we
|
||||
are actually importing from `evennia/__init__.py`.
|
||||
|
||||
[Look at Line 189](evennia/__init__.py#L189) of `evennia/__init__.py` and you'll find this line:
|
||||
|
||||
from .objects.objects import DefaultObject
|
||||
|
||||
```sidebar:: Relative and absolute imports
|
||||
|
||||
The first full-stop in `from .objects.objects ...` means that
|
||||
we are importing from the current location. This is called a `relative import`.
|
||||
By comparison, `from evennia.objects.objects` is an `absolute import`. In this particular
|
||||
case, the two would give the same result.
|
||||
```
|
||||
|
||||
> You can also look at [the right section of the API frontpage](../../../Evennia-API#typeclasses) and click through
|
||||
> to the code that way.
|
||||
|
||||
The fact that `DefaultObject` is imported into `__init__.py` here is what makes it possible to also import
|
||||
it as `from evennia import DefaultObject` even though the code for the class is not actually here.
|
||||
|
||||
So to find the code for `DefaultObject` we need to look in `evennia/objects/objects.py`. Here's how
|
||||
to look it up in the docs:
|
||||
|
||||
1. Open the [API frontpage](../../../Evennia-API)
|
||||
2. Locate the link to [evennia.objects](api:evennia.objects) and click on it.
|
||||
3. Click through to [evennia.objects.objects](api:evennia.objects.objects).
|
||||
4. You are now in the python module. Scroll down (or search in your web browser) to find the `DefaultObject` class.
|
||||
5. You can now read what this does and what methods are on it. If you want to see the full source, click the
|
||||
\[[source](src:evennia.objects.objects#DefaultObject)\] link.
|
||||
|
||||
[prev lesson](Python-classes-and-objects) | [next lesson](Learning-Typeclasses)
|
||||
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
# Persistent objects and typeclasses
|
||||
|
||||
[prev lesson](Python-classes-and-objects) | [next lesson](Adding-Commands)
|
||||
[prev lesson](Evennia-Library-Overview) | [next lesson](Creating-Things)
|
||||
|
||||
In the last lesson we created the dragons Fluffy, Cuddly and Smaug and made the fly and breathe fire. We
|
||||
learned a bit about _classes_ in the process. But so far our dragons are short-lived - whenever we `restart`
|
||||
Now that we have learned a little about how to find things in the Evennia library, let's use it.
|
||||
|
||||
In the [Python classes and objects](Python-classes-and-objects) lesson we created the dragons Fluffy, Cuddly
|
||||
and Smaug and made them fly and breathe fire. So far our dragons are short-lived - whenever we `restart`
|
||||
the server or `quit()` out of python mode they are gone.
|
||||
|
||||
This is what you should have in `mygame/typeclasses/monsters.py` so far:
|
||||
|
|
@ -620,4 +622,4 @@ Typeclasses are a fundamental part of Evennia and we will see a lot of more uses
|
|||
this tutorial. But that's enough of them for now. It's time to take some action. Let's learn about _Commands_.
|
||||
|
||||
|
||||
[prev lesson](Python-classes-and-objects) | [next lesson](Adding-Commands)
|
||||
[prev lesson](Evennia-Library-Overview) | [next lesson](Creating-Things)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# More about Commands
|
||||
|
||||
[prev lesson](Adding-Commands) | [next lesson](Evennia-API-Overview)
|
||||
[prev lesson](Adding-Commands) | [next lesson](Creating-Things)
|
||||
|
||||
In this lesson we learn some basics about parsing the input of Commands. We will
|
||||
also learn how to add, modify and extend Evennia's default commands.
|
||||
|
|
@ -497,8 +497,4 @@ In this lesson we got into some more advanced string formatting - many of those
|
|||
the future! We also made a functional sword. Finally we got into how to add to, extend and replace a default
|
||||
command on ourselves.
|
||||
|
||||
In the last few lessons we have made use of resources from Evennia. Now that we have had some experience of how
|
||||
classes and inheritance work, we can start exploring this in earnest.
|
||||
|
||||
|
||||
[prev lesson](Adding-Commands) | [next lesson](Evennia-API-Overview)
|
||||
[prev lesson](Adding-Commands) | [next lesson](Creating-Things)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Python Classes and objects
|
||||
|
||||
[prev lesson](Gamedir-Overview) | [next lesson](Learning-Typeclasses)
|
||||
[prev lesson](Gamedir-Overview) | [next lesson](Evennia-Library-Overview)
|
||||
|
||||
We have now learned how to run some simple Python code from inside (and outside) your game server.
|
||||
We have also taken a look at what our game dir looks and what is where. Now we'll start to use it.
|
||||
|
|
@ -409,8 +409,7 @@ We have created our first dragons from classes. We have learned a little about h
|
|||
into an _object_. We have seen some examples of _inheritance_ and we tested to _override_ a method in the parent
|
||||
with one in the child class. We also used `super()` to good effect.
|
||||
|
||||
But so far our dragons are gone as soon as we `restart` the server or `quit()` the Python interpreter. In the
|
||||
next lesson we'll get up close and personal with Smaug.
|
||||
We have used pretty much raw Python so far. In the coming lessons we'll start to look at the extra bits that Evennia
|
||||
provides. But first we need to learn just where to find everything.
|
||||
|
||||
|
||||
[prev lesson](Gamedir-Overview) | [next lesson](Learning-Typeclasses)
|
||||
[prev lesson](Gamedir-Overview) | [next lesson](Evennia-Library-Overview)
|
||||
|
|
|
|||
576
docs/source/Howto/Starting/Part1/Searching-Things.md
Normal file
576
docs/source/Howto/Starting/Part1/Searching-Things.md
Normal file
|
|
@ -0,0 +1,576 @@
|
|||
# Searching for things
|
||||
|
||||
[prev lesson](Creating-Things) | [next lesson]()
|
||||
|
||||
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.
|
||||
|
||||
## Main search functions
|
||||
|
||||
The base tools are the `evennia.search_*` functions, such as `evennia.search_object`.
|
||||
|
||||
rose = evennia.search_object(key="rose")
|
||||
acct = evennia.search_account(key="MyAccountName", email="foo@bar.com")
|
||||
|
||||
```sidebar:: Querysets
|
||||
|
||||
What is returned from the main search functions is actually a `queryset`. They can be
|
||||
treated like lists except that they can't modified in-place. We'll discuss querysets at
|
||||
the end of this lesson.
|
||||
```
|
||||
|
||||
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 0, one or more
|
||||
elements - all the matches to your search. To get the first match:
|
||||
|
||||
rose = rose[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](../../../Evennia-API) of the API frontpage.
|
||||
|
||||
## 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")
|
||||
|
||||
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` and `obj.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 to `obj`.
|
||||
|
||||
So this method handles error messaging for you. A very common way to use it is in commands:
|
||||
|
||||
```python
|
||||
from evennia import Command
|
||||
|
||||
class MyCommand(Command):
|
||||
|
||||
key = "findfoo"
|
||||
|
||||
def func(self):
|
||||
|
||||
foo = self.caller.search("foo")
|
||||
if not foo:
|
||||
return
|
||||
```
|
||||
|
||||
Remember, `self.caller` is the one calling the command. This is usually a Character, which
|
||||
inherits from `DefaultObject`! This (rather stupid) Command searches for an object named "foo" in
|
||||
the same location. If it can't find it, `foo` will be `None`. The error has already been reported
|
||||
to `self.caller` so we just abort with `return`.
|
||||
|
||||
You can use `.search` to find anything, not just stuff in the same room:
|
||||
|
||||
volcano = self.caller.search("Volcano", global=True)
|
||||
|
||||
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 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.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 and what you get back is now a list of zero, one or more matches!
|
||||
|
||||
## What can be searched for
|
||||
|
||||
These are the main database entities one can search for:
|
||||
|
||||
- [Objects](../../../Component/Objects)
|
||||
- [Accounts](../../../Component/Accounts)
|
||||
- [Scripts](../../../Component/Scripts),
|
||||
- [Channels](../../../Component/Communications#channels),
|
||||
- [Messages](Communication#Msg)
|
||||
- [Help Entries](../../../Component/Help-System).
|
||||
|
||||
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?
|
||||
|
||||
### Search by key
|
||||
|
||||
The `key` is the name of the entity. Searching for this is always case-insensitive.
|
||||
|
||||
### 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.
|
||||
|
||||
### Search by location
|
||||
|
||||
Only Objects (things inheriting from `evennia.DefaultObject`) has a location. This 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)
|
||||
|
||||
### Search by Tags
|
||||
|
||||
Think of a [Tag](../../../Component/Tags) as the label the airport puts on your luggage when flying.
|
||||
Everyone going on the same plane gets a tag grouping them together so the airport can know what should
|
||||
go to which plane. Entities in Evennia can be grouped in the same way. Any number of tags can be attached
|
||||
to each object.
|
||||
|
||||
rose.tags.add("flowers")
|
||||
daffodil.tags.add("flowers")
|
||||
tulip.tags.add("flowers")
|
||||
|
||||
You can now find all flowers using the `search_tag` function:
|
||||
|
||||
all_flowers = evennia.search_tag("flowers")
|
||||
|
||||
Tags can also have categories. By default this category is `None` which is also considered a category.
|
||||
|
||||
silmarillion.tags.add("fantasy", category="books")
|
||||
ice_and_fire.tags.add("fantasy", category="books")
|
||||
mona_lisa_overdrive.tags.add("cyberpunk", category="books")
|
||||
|
||||
Note that if you specify the tag you _must_ also include its category, otherwise that category
|
||||
will be `None` and find no matches.
|
||||
|
||||
all_fantasy_books = evennia.search_tag("fantasy") # no matches!
|
||||
all_fantasy_books = evennia.search_tag("fantasy", category="books")
|
||||
|
||||
Only the second line above returns the two fantasy books. If we specify a category however,
|
||||
we can get all tagged entities within that category:
|
||||
|
||||
all_books = evennia.search_tag(category="books")
|
||||
|
||||
This gets all three books.
|
||||
|
||||
### Search by Attribute
|
||||
|
||||
We can also search by the [Attributes](../../../Component/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.
|
||||
|
||||
|
||||
### 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. We'll cover this some more as an [Extra-credits](#Extra-Credits) section at the end of this lesson.
|
||||
|
||||
### 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
|
||||
|
||||
You may be used to using #dbrefs a lot from other codebases. It is however considered
|
||||
`bad practice` in Evennia to rely on hard-coded #dbrefs. It makes your code hard to maintain
|
||||
and tied to the exact layout of the database. In 99% of cases you should pass the actual objects
|
||||
around and search by key/tags/attribute instead.
|
||||
```
|
||||
|
||||
## Finding objects relative each other
|
||||
|
||||
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.
|
||||
|
||||
- `coin.location` is `chest`.
|
||||
- `chest.location` is `dungeon`.
|
||||
- `door.location` is `dungeon`.
|
||||
- `room.location` is `None` 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` is `outside` (or wherever the door leads)
|
||||
- `room.destination` is `None` (same for all the other non-exit objects)
|
||||
|
||||
## Database queries
|
||||
|
||||
The search functions and methods above are enough for most cases. But sometimes you need to be
|
||||
more specific:
|
||||
|
||||
- You want to find all `Characters` ...
|
||||
- ... who are in Rooms tagged as `moonlit` ...
|
||||
- ... _and_ who has the Attribute `lycantrophy` with a level higher than 2 ...
|
||||
- ... because they'll immediately become werewolves!
|
||||
|
||||
In principle you could achieve this with the existing search functions combined with a lot of loops
|
||||
and if statements. But for something non-standard like this querying the database directly will be
|
||||
more efficient.
|
||||
|
||||
A [django queryset](https://docs.djangoproject.com/en/3.0/ref/models/querysets/) represents
|
||||
a database query. One can add querysets together to build ever-more complicated queries. Only when
|
||||
you are trying to use the results of the queryset will it actually call the database.
|
||||
|
||||
The normal way to build a queryset is to define what class of entity you want to search by getting its
|
||||
`.objects` resource, and then call various methods on that. We've seen this one before:
|
||||
|
||||
all_weapons = Weapon.objects.all()
|
||||
|
||||
This is now a queryset representing all instances of `Weapon`. If `Weapon` had a subclass `Cannon` and we
|
||||
only wanted the cannons, we would do
|
||||
|
||||
all_cannons = Cannon.objects.all()
|
||||
|
||||
Note that `Weapon` and `Cannon` are different typeclasses. You won't find any `Cannon` instances in
|
||||
the `all_weapon` result above, confusing as that may sound. To get instances of a Typeclass _and_ the
|
||||
instances of all its children classes you need to use `_family`:
|
||||
|
||||
```sidebar:: _family
|
||||
|
||||
The all_family, filter_family etc is an Evennia-specific
|
||||
thing. It's not part of regular Django.
|
||||
|
||||
```
|
||||
|
||||
really_all_weapons = Weapon.objects.all_family()
|
||||
|
||||
This result now contains both `Weapon` and `Cannon` instances.
|
||||
|
||||
To actually limit your search by other criteria than the Typeclass you need to use `.filter`
|
||||
(or `.filter_family`) instead:
|
||||
|
||||
roses = Flower.objects.filter(db_key="rose")
|
||||
|
||||
This is a queryset representing all objects having a `db_key` equal to `"rose"`.
|
||||
Since this is a queryset you can keep adding to it:
|
||||
|
||||
local_roses = roses.filter(db_location=myroom)
|
||||
|
||||
We could also have written this in one statement:
|
||||
|
||||
local_roses = Flower.objects.filter(db_key="rose", db_location=myroom)
|
||||
|
||||
We can also `.exclude` something from results
|
||||
|
||||
local_non_red_roses = local_roses.exclude(db_key="red_rose")
|
||||
|
||||
Only until we actually try to examine the result will the database be called. Here it's called when we
|
||||
try to loop over the queryset:
|
||||
|
||||
for rose in local_non_red_roses:
|
||||
print(rose)
|
||||
|
||||
From now on, the queryset is _evaluated_ and we can't keep adding more queries to it - we'd need to
|
||||
create a new queryset if we wanted to find some other result.
|
||||
|
||||
Note how we use `db_key` and `db_location`. This is the actual names of these database fields. By convention
|
||||
Evennia uses `db_` in front of every database field, but when you access it in Python you can skip the `db_`. This
|
||||
is why you can use `obj.key` and `obj.location` in normal code. Here we are calling the database directly though
|
||||
and need to use the 'real' names.
|
||||
|
||||
Here are the most commonly used methods to use with the `objects` managers:
|
||||
|
||||
- `filter` - query for a listing of objects based on search criteria. Gives empty queryset if none
|
||||
were found.
|
||||
- `get` - query for a single match - raises exception if none were found, or more than one was
|
||||
found.
|
||||
- `all` - get all instances of the particular type.
|
||||
- `filter_family` - like `filter`, but search all sub classes as well.
|
||||
- `get_family` - like `get`, but search all sub classes as well.
|
||||
- `all_family` - like `all`, but return entities of all subclasses as well.
|
||||
|
||||
> All of Evennia search functions use querysets under the hood. The `evennia.search_*` functions actually
|
||||
> return querysets, which means you could in principle keep adding queries to their results as well.
|
||||
|
||||
|
||||
### Queryset field lookups
|
||||
|
||||
Above we found roses with exactly the `db_key` `"rose"`. This is an _exact_ match that is _case sensitive_,
|
||||
so it would not find `"Rose"`.
|
||||
|
||||
# this is case-sensitive and the same as =
|
||||
roses = Flower.objects.filter(db_key__exact="rose"
|
||||
# the i means it's case-insensitive
|
||||
roses = Flower.objects.filter(db_key__iequals="rose")
|
||||
|
||||
The Django field query language uses `__` in the same way as Python uses `.` to access resources. This
|
||||
is because `.` is not allowed in a function keyword.
|
||||
|
||||
roses = Flower.objects.filter(db_key__icontains="rose")
|
||||
|
||||
This will find all flowers whose name contains the string `"rose"`, like `"roses"`, `"wild rose"` etc. The
|
||||
`i` in the beginning makes the search case-insensitive. Other useful variations to use
|
||||
are `__istartswith` and `__iendswith`. You can also use `__gt`, `__ge` for "greater-than"/"greater-or-equal-than"
|
||||
comparisons (same for `__lt` and `__le`). There is also `__in`:
|
||||
|
||||
swords = Weapons.objects.filter(db_key__in=("rapier", "two-hander", "shortsword"))
|
||||
|
||||
For more field lookups, see the
|
||||
[django docs](https://docs.djangoproject.com/en/3.0/ref/models/querysets/#field-lookups) on the subject.
|
||||
|
||||
### Get that werewolf ...
|
||||
|
||||
Let's see if we can make a query for the werewolves in the moonlight we mentioned at the beginning
|
||||
of this section.
|
||||
|
||||
Firstly, we make ourselves and our current location match the criteria, so we can test:
|
||||
|
||||
> py here.tags.add("moonlit")
|
||||
> py me.db.lycantrophy = 3
|
||||
|
||||
This is an example of a more complex query. We'll consider it an example of what is
|
||||
possible.
|
||||
|
||||
```sidebar:: Line breaks
|
||||
|
||||
Note the way of writing this code. It would have been very hard to read if we just wrote it in
|
||||
one long line. But since we wrapped it in `(...)` we can spread it out over multiple lines
|
||||
without worrying about line breaks!
|
||||
```
|
||||
|
||||
```python
|
||||
from typeclasses.characters import Character
|
||||
|
||||
will_transform = (
|
||||
Character.objects
|
||||
.filter(
|
||||
db_location__db_tags__db_key__iexact="moonlit",
|
||||
db_attributes__db_key="lycantrophy",
|
||||
db_attributes__db_value__gt=2)
|
||||
)
|
||||
```
|
||||
|
||||
- **Line 3** - We want to find `Character`s, so we access `.objects` on the `Character` typeclass.
|
||||
- **Line 4** - We start to filter ...
|
||||
- **Line 5**
|
||||
- ... by accessing the `db_location` field (usually this is a Room)
|
||||
- ... and on that location, we get the value of `db_tags` (this is a _many-to-many_ database field
|
||||
that we can treat like an object for this purpose; it references all Tags on the location)
|
||||
- ... and from those `Tags`, we looking for `Tags` whose `db_key` is "monlit" (non-case sensitive).
|
||||
- **Line 6** - ... We also want only Characters with `Attributes` whose `db_key` is exactly `"lycantrophy"`
|
||||
- **Line 7** - ... at the same time as the `Attribute`'s `db_value` is greater-than 2.
|
||||
|
||||
Running this query makes our newly lycantrrophic Character appear in `will_transform`. Success!
|
||||
|
||||
> Don't confuse database fields with [Attributes](../../../Component/Attributes) you set via `obj.db.attr = 'foo'` or
|
||||
`obj.attributes.add()`. Attributes are custom database entities *linked* to an object. They are not
|
||||
separate fields *on* that object like `db_key` or `db_location` are.
|
||||
|
||||
### Complex queries
|
||||
|
||||
All examples so far used `AND` relations. The arguments to `.filter` are added together with `AND`
|
||||
("we want tag room to be "monlit" _and_ lycantrhopy be > 2").
|
||||
|
||||
For queries using `OR` and `NOT` we need Django's
|
||||
[Q object](https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q-objects). It is
|
||||
import from Django directly:
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
`Q()` objects take the same arguments like `.filter`:
|
||||
|
||||
Q(db_key="foo")
|
||||
|
||||
The special thing is that these `Q` objects can then be chained together with special symbols:
|
||||
`|` for `OR`, `&` for `AND`. A tilde `~` in front negates the expression inside the `Q` and thus works like `NOT`.
|
||||
|
||||
Let us expand our original werewolf query. Not only do we want to find all Characters in a moonlit room
|
||||
with a certain level of `lycanthrophy`. Now we also want the full moon to immediately transform people who were
|
||||
recently bitten, even if their `lycantrophy` level is not yet high enough (more dramatic this way!). Let's say there is
|
||||
a Tag "recently_bitten" that controls this.
|
||||
|
||||
This is how we'd change our query:
|
||||
|
||||
```python
|
||||
from django.db.models import Q
|
||||
|
||||
will_transform = (
|
||||
|
||||
Character.objects
|
||||
.filter(
|
||||
Q(db_location__db_tags__db_key__iexact="moonlit")
|
||||
& (
|
||||
Q(db_attributes__db_key="lycantrophy",
|
||||
db_attributes__db_value__gt=2)
|
||||
| Q(db__tags__db__key__iexact="recently_bitten")
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
We now grouped the filter
|
||||
|
||||
In our original Lycanthrope example we wanted our werewolves to have names that could start with any
|
||||
vowel except for the specific beginning "ab".
|
||||
|
||||
```python
|
||||
from django.db.models import Q
|
||||
from typeclasses.characters import Character
|
||||
|
||||
query = Q()
|
||||
for letter in ("aeiouy"):
|
||||
query |= Q(db_key__istartswith=letter)
|
||||
query &= ~Q(db_key__istartswith="ab")
|
||||
query = Character.objects.filter(query)
|
||||
|
||||
list_of_lycanthropes = list(query)
|
||||
```
|
||||
|
||||
In the above example, we construct our query our of several Q objects that each represent one part
|
||||
of the query. We iterate over the list of vowels, and add an `OR` condition to the query using `|=`
|
||||
(this is the same idea as using `+=` which may be more familiar). Each `OR` condition checks that
|
||||
the name starts with one of the valid vowels. Afterwards, we add (using `&=`) an `AND` condition
|
||||
that is negated with the `~` symbol. In other words we require that any match should *not* start
|
||||
with the string "ab". Note that we don't actually hit the database until we convert the query to a
|
||||
list at the end (we didn't need to do that either, but could just have kept the query until we
|
||||
needed to do something with the matches).
|
||||
|
||||
### Annotations and `F` objects
|
||||
|
||||
What if we wanted to filter on some condition that isn't represented easily by a field on the
|
||||
object? Maybe we want to find rooms only containing five or more objects?
|
||||
|
||||
We *could* retrieve all interesting candidates and run them through a for-loop to get and count
|
||||
their `.content` properties. We'd then just return a list of only those objects with enough
|
||||
contents. It would look something like this (note: don't actually do this!):
|
||||
|
||||
```python
|
||||
# probably not a good idea to do it this way
|
||||
|
||||
from typeclasses.rooms import Room
|
||||
|
||||
queryset = Room.objects.all() # get all Rooms
|
||||
rooms = [room for room in queryset if len(room.contents) >= 5]
|
||||
|
||||
```
|
||||
|
||||
Once the number of rooms in your game increases, this could become quite expensive. Additionally, in
|
||||
some particular contexts, like when using the web features of Evennia, you must have the result as a
|
||||
queryset in order to use it in operations, such as in Django's admin interface when creating list
|
||||
filters.
|
||||
|
||||
Enter [F objects](https://docs.djangoproject.com/en/1.11/ref/models/expressions/#f-expressions) and
|
||||
*annotations*. So-called F expressions allow you to do a query that looks at a value of each object
|
||||
in the database, while annotations allow you to calculate and attach a value to a query. So, let's
|
||||
do the same example as before directly in the database:
|
||||
|
||||
```python
|
||||
from typeclasses.rooms import Room
|
||||
from django.db.models import Count
|
||||
|
||||
room_count = Room.objects.annotate(num_objects=Count('locations_set'))
|
||||
queryset = room_count.filter(num_objects__gte=5)
|
||||
|
||||
rooms = (Room.objects.annotate(num_objects=Count('locations_set'))
|
||||
.filter(num_objects__gte=5))
|
||||
|
||||
rooms = list(rooms)
|
||||
|
||||
```
|
||||
Here we first create an annotation `num_objects` of type `Count`, which is a Django class. Note that
|
||||
use of `location_set` in that `Count`. The `*_set` is a back-reference automatically created by
|
||||
Django. In this case it allows you to find all objects that *has the current object as location*.
|
||||
Once we have those, they are counted.
|
||||
Next we filter on this annotation, using the name `num_objects` as something we can filter for. We
|
||||
use `num_objects__gte=5` which means that `num_objects` should be greater than 5. This is a little
|
||||
harder to get one's head around but much more efficient than lopping over all objects in Python.
|
||||
|
||||
What if we wanted to compare two parameters against one another in a query? For example, what if
|
||||
instead of having 5 or more objects, we only wanted objects that had a bigger inventory than they
|
||||
had tags? Here an F-object comes in handy:
|
||||
|
||||
```python
|
||||
from django.db.models import Count, F
|
||||
from typeclasses.rooms import Room
|
||||
|
||||
result = (Room.objects.annotate(num_objects=Count('locations_set'),
|
||||
num_tags=Count('db_tags'))
|
||||
.filter(num_objects__gt=F('num_tags')))
|
||||
```
|
||||
|
||||
F-objects allows for wrapping an annotated structure on the right-hand-side of the expression. It
|
||||
will be evaluated on-the-fly as needed.
|
||||
|
||||
### Grouping By and Values
|
||||
|
||||
Suppose you used tags to mark someone belonging an organization. Now you want to make a list and
|
||||
need to get the membership count of every organization all at once. That's where annotations and the
|
||||
`.values_list` queryset method come in. Values/Values Lists are an alternate way of returning a
|
||||
queryset - instead of objects, you get a list of dicts or tuples that hold selected properties from
|
||||
the the matches. It also allows you a way to 'group up' queries for returning information. For
|
||||
example, to get a display about each tag per Character and the names of the tag:
|
||||
|
||||
```python
|
||||
result = (Character.objects.filter(db_tags__db_category="organization")
|
||||
.values_list('db_tags__db_key')
|
||||
.annotate(cnt=Count('id'))
|
||||
.order_by('-cnt'))
|
||||
```
|
||||
The result queryset will be a list of tuples ordered in descending order by the number of matches,
|
||||
in a format like the following:
|
||||
```
|
||||
[('Griatch Fanclub', 3872), ("Chainsol's Ainneve Testers", 2076), ("Blaufeuer's Whitespace Fixers",
|
||||
1903),
|
||||
("Volund's Bikeshed Design Crew", 1764), ("Tehom's Misanthropes", 1)]
|
||||
|
||||
[prev lesson](Creating-Things) | [next lesson]()
|
||||
|
|
@ -132,7 +132,7 @@ Talker-type game you *will* have to bite the bullet and code your game (or find
|
|||
do it for you).
|
||||
|
||||
Even if you won't code anything yourself, as a designer you need to at least understand the basic
|
||||
paradigms of Evennia, such as [Objects](../../Component/Objects), [Commands](../../Component/Commands) and [Scripts](../../Component/Scripts) and
|
||||
paradigms of Evennia, such as [Objects](../../../Component/Objects), [Commands](../../../Component/Commands) and [Scripts](../../../Component/Scripts) and
|
||||
how they hang together. We recommend you go through the [Tutorial World](Tutorial-World-
|
||||
Introduction) in detail (as well as glancing at its code) to get at least a feel for what is
|
||||
involved behind the scenes. You could also look through the tutorial for [building a game from
|
||||
|
|
@ -144,7 +144,7 @@ The earlier you revise problems, the easier they will be to fix.
|
|||
|
||||
A good idea is to host your code online (publicly or privately) using version control. Not only will
|
||||
this make it easy for multiple coders to collaborate (and have a bug-tracker etc), it also means
|
||||
your work is backed up at all times. The [Version Control](../../Coding/Version-Control) tutorial has
|
||||
your work is backed up at all times. The [Version Control](../../../Coding/Version-Control) tutorial has
|
||||
instructions for setting up a sane developer environment with proper version control.
|
||||
|
||||
### "Tech Demo" Building
|
||||
|
|
@ -199,7 +199,7 @@ flag and let people try it! Call upon your alpha-players to try everything - the
|
|||
to break your game in ways that you never could have imagined. In Alpha you might be best off to
|
||||
focus on inviting friends and maybe other MUD developers, people who you can pester to give proper
|
||||
feedback and bug reports (there *will* be bugs, there is no way around it). Follow the quick
|
||||
instructions for [Online Setup](../../Setup/Online-Setup) to make your game visible online. If you hadn't
|
||||
instructions for [Online Setup](../../../Setup/Online-Setup) to make your game visible online. If you hadn't
|
||||
already, make sure to put up your game on the [Evennia game index](http://games.evennia.com/) so
|
||||
people know it's in the works (actually, even pre-alpha games are allowed in the index so don't be
|
||||
shy)!
|
||||
|
|
@ -27,11 +27,12 @@ own first little game in Evennia. Let's get started!
|
|||
1. [Python basics](Part1/Python-basic-introduction)
|
||||
1. [Game dir overview](Part1/Gamedir-Overview)
|
||||
1. [Python classes and objects](Part1/Python-classes-and-objects)
|
||||
1. [Persistent objects](Part1/Learning-Typeclasses)
|
||||
1. [Accessing the Evennia library](Part1/Evennia-Library-Overview)
|
||||
1. [Typeclasses - Persistent objects](Part1/Learning-Typeclasses)
|
||||
1. [Making our first own commands](Part1/Adding-Commands)
|
||||
1. [Parsing and replacing default Commands](Part1/More-on-Commands)
|
||||
1. [Searching and creating things](Tutorial-Searching-For-Objects)
|
||||
1. [A walkthrough of the API](Walkthrough-of-API)
|
||||
1. [Creating things](Part1/Creating-Things)
|
||||
1. [Searching for things](Part1/Searching-Things)
|
||||
|
||||
In this first part we'll focus on what we get out of the box in Evennia - we'll get used to the tools,
|
||||
where things are and how we find things we are looking for. We will also dive into some of things you'll
|
||||
|
|
|
|||
|
|
@ -92,22 +92,23 @@
|
|||
- [Howto/Manually Configuring Color](Howto/Manually-Configuring-Color)
|
||||
- [Howto/Mass and weight for objects](Howto/Mass-and-weight-for-objects)
|
||||
- [Howto/NPC shop Tutorial](Howto/NPC-shop-Tutorial)
|
||||
- [Howto/Starting/API Overview](Howto/Starting/API-Overview)
|
||||
- [Howto/Starting/Add a simple new web page](Howto/Starting/Add-a-simple-new-web-page)
|
||||
- [Howto/Starting/Coordinates](Howto/Starting/Coordinates)
|
||||
- [Howto/Starting/First Steps Coding](Howto/Starting/First-Steps-Coding)
|
||||
- [Howto/Starting/Game Planning](Howto/Starting/Game-Planning)
|
||||
- [Howto/Starting/Implementing a game rule system](Howto/Starting/Implementing-a-game-rule-system)
|
||||
- [Howto/Starting/Parsing command arguments, theory and best practices](Howto/Starting/Parsing-command-arguments,-theory-and-best-practices)
|
||||
- [Howto/Starting/Part1/Adding Commands](Howto/Starting/Part1/Adding-Commands)
|
||||
- [Howto/Starting/Part1/Building Quickstart](Howto/Starting/Part1/Building-Quickstart)
|
||||
- [Howto/Starting/Part1/Evennia API Overview](Howto/Starting/Part1/Evennia-API-Overview)
|
||||
- [Howto/Starting/Part1/Creating Things](Howto/Starting/Part1/Creating-Things)
|
||||
- [Howto/Starting/Part1/Evennia Library Overview](Howto/Starting/Part1/Evennia-Library-Overview)
|
||||
- [Howto/Starting/Part1/Gamedir Overview](Howto/Starting/Part1/Gamedir-Overview)
|
||||
- [Howto/Starting/Part1/Learning Typeclasses](Howto/Starting/Part1/Learning-Typeclasses)
|
||||
- [Howto/Starting/Part1/More on Commands](Howto/Starting/Part1/More-on-Commands)
|
||||
- [Howto/Starting/Part1/Python basic introduction](Howto/Starting/Part1/Python-basic-introduction)
|
||||
- [Howto/Starting/Part1/Python classes and objects](Howto/Starting/Part1/Python-classes-and-objects)
|
||||
- [Howto/Starting/Part1/Searching Things](Howto/Starting/Part1/Searching-Things)
|
||||
- [Howto/Starting/Part1/Tutorial World Introduction](Howto/Starting/Part1/Tutorial-World-Introduction)
|
||||
- [Howto/Starting/Part2/Game Planning](Howto/Starting/Part2/Game-Planning)
|
||||
- [Howto/Starting/Starting Part1](Howto/Starting/Starting-Part1)
|
||||
- [Howto/Starting/Starting Part2](Howto/Starting/Starting-Part2)
|
||||
- [Howto/Starting/Starting Part3](Howto/Starting/Starting-Part3)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue