Finished typeclass docs
This commit is contained in:
parent
a515ececff
commit
a8f56f4370
6 changed files with 553 additions and 160 deletions
|
|
@ -9,8 +9,15 @@ A Command is something that handles the input from a user and causes a result to
|
||||||
An example is `look`, which examines your current location and tells how it looks like and
|
An example is `look`, which examines your current location and tells how it looks like and
|
||||||
what is in it.
|
what is in it.
|
||||||
|
|
||||||
|
```sidebar:: Commands are not typeclassed
|
||||||
|
|
||||||
|
If you just came from the previous lesson, you might want to know that Commands and
|
||||||
|
CommandSets are not `typeclassed`. That is, instances of them are not saved to the
|
||||||
|
database. They are "just" normal Python classes.
|
||||||
|
```
|
||||||
|
|
||||||
In Evennia, a Command is a Python _class_. If you are unsure about what a class is, review the
|
In Evennia, a Command is a Python _class_. If you are unsure about what a class is, review the
|
||||||
previous lesson. A Command inherits from `evennia.Command` or from one of the alternative command-
|
previous lessons! A Command inherits from `evennia.Command` or from one of the alternative command-
|
||||||
classes, such as `MuxCommand` which is what most default commands use.
|
classes, such as `MuxCommand` which is what most default commands use.
|
||||||
|
|
||||||
All Commands are in turn grouped in another class called a _Command Set_. Think of a Command Set
|
All Commands are in turn grouped in another class called a _Command Set_. Think of a Command Set
|
||||||
|
|
@ -18,8 +25,8 @@ as a bag holding many different commands. One CmdSet could for example hold all
|
||||||
combat, another for building etc. By default, Evennia groups all character-commands into one
|
combat, another for building etc. By default, Evennia groups all character-commands into one
|
||||||
big cmdset.
|
big cmdset.
|
||||||
|
|
||||||
Command-Sets are then associated with objects. Doing so makes the commands in that cmdset available
|
Command-Sets are then associated with objects, for example with your Character. Doing so makes the
|
||||||
to the object. So, to summarize:
|
commands in that cmdset available to the object. So, to summarize:
|
||||||
|
|
||||||
- Commands are classes
|
- Commands are classes
|
||||||
- A group of Commands is stored in a CmdSet
|
- A group of Commands is stored in a CmdSet
|
||||||
|
|
@ -380,7 +387,7 @@ You won't see the second string. Only Smaug sees that (and is not amused).
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
In this lesson we learned how to create our own Command, add it to a CmdSet and then ourselves.
|
In this lesson we learned how to create our own Command, add it to a CmdSet and then to ourselves.
|
||||||
We also upset a dragon.
|
We also upset a dragon.
|
||||||
|
|
||||||
In the next lesson we'll learn how to hit Smaug with different weapons. We'll also
|
In the next lesson we'll learn how to hit Smaug with different weapons. We'll also
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,69 @@
|
||||||
# Tutorial Searching For Objects
|
# 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 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. You can examine this in many ways:
|
||||||
|
|
||||||
|
- The easiest is to browse the [API auto-docs](api:evennia) coming with this very documentation. This is built
|
||||||
|
automatically from the latest sources. The auto-docs give you each class, function and method along with the
|
||||||
|
docstring and everything you need to use that resource. If you want to go deeper you can also click the `[src]`
|
||||||
|
link next to e.g. a class to see its full python code. The documentation is also searchable.
|
||||||
|
- 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 "class DefaultObject"
|
||||||
|
|
||||||
|
will quickly tell you where the DefaultObject class is defined.
|
||||||
|
|
||||||
|
### Side note for those reading the code directly (optional)
|
||||||
|
|
||||||
|
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 library
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Tutorial Searching For Objects
|
||||||
|
|
||||||
You will often want to operate on a specific object in the database. For example when a player
|
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
|
attacks a named target you'll need to find that target so it can be attacked. Or when a rain storm
|
||||||
|
|
@ -9,8 +73,8 @@ explains Evennia's tools for searching.
|
||||||
## Things to search for
|
## Things to search for
|
||||||
|
|
||||||
The first thing to consider is the base type of the thing you are searching for. Evennia organizes
|
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),
|
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).
|
[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.
|
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?
|
So to find an entity, what can be searched for?
|
||||||
|
|
@ -22,20 +86,20 @@ the database field for `.key` is instead named `username` (this is a Django requ
|
||||||
don't specify search-type, you'll usually search based on key. *Aliases* are extra names given to
|
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
|
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.
|
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
|
- [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
|
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
|
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
|
implements 'zones' seen in other systems). Tags can also have categories, to further organize your
|
||||||
data for quick lookups.
|
data for quick lookups.
|
||||||
- An object's [Attributes](../../Component/Attributes) can also used to find an object. This can be very useful but
|
- 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
|
since Attributes can store almost any data they are far less optimized to search for than Tags or
|
||||||
keys.
|
keys.
|
||||||
- The object's [Typeclass](../../Component/Typeclasses) indicate the sub-type of entity. A Character, Flower or
|
- 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
|
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_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
|
`typeclass` as an argument to Evennia's search functions as well as use the class directly to limit
|
||||||
queries.
|
queries.
|
||||||
- The `location` is only relevant for [Objects](../../Component/Objects) but is a very common way to weed down the
|
- 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
|
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.
|
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
|
- The database id or the '#dbref' is unique (and never re-used) within each database table. So while
|
||||||
|
|
@ -50,7 +114,7 @@ around and searching by Tags and/or keys will usually get you what you need.
|
||||||
|
|
||||||
## Getting objects inside another
|
## Getting objects inside another
|
||||||
|
|
||||||
All in-game [Objects](../../Component/Objects) have a `.contents` property that returns all objects 'inside' them
|
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
|
(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.
|
get everything in a room and is also faster since this lookup is cached and won't hit the database.
|
||||||
|
|
||||||
|
|
@ -64,7 +128,7 @@ location except `obj`.
|
||||||
|
|
||||||
## Searching using `Object.search`
|
## Searching using `Object.search`
|
||||||
|
|
||||||
Say you have a [command](../../Component/Commands), and you want it to do something to a target. You might be
|
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.
|
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`
|
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
|
typeclasses. In a command, the `.caller` property will refer back to the object using the command
|
||||||
|
|
@ -133,7 +197,7 @@ class CmdListHangouts(default_cmds.MuxCommand):
|
||||||
", ".join(str(ob) for ob in hangouts)))
|
", ".join(str(ob) for ob in hangouts)))
|
||||||
```
|
```
|
||||||
|
|
||||||
This uses the `search_tag` function to find all objects previously tagged with [Tags](../../Component/Tags)
|
This uses the `search_tag` function to find all objects previously tagged with [Tags](../../../Component/Tags)
|
||||||
"hangout" and with category "location tags".
|
"hangout" and with category "location tags".
|
||||||
|
|
||||||
Other important search methods in `utils.search` are
|
Other important search methods in `utils.search` are
|
||||||
|
|
@ -303,7 +367,7 @@ nice enough to alias the `db_key` field so you can normally just do `char.key` t
|
||||||
name, the database field is actually called `db_key` and the real name must be used for the purpose
|
name, the database field is actually called `db_key` and the real name must be used for the purpose
|
||||||
of building a query.
|
of building a query.
|
||||||
|
|
||||||
> Don't confuse database fields with [Attributes](../../Component/Attributes) you set via `obj.db.attr = 'foo'` or
|
> 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
|
`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
|
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.
|
manually through the `db_attributes` many-to-many field in the same way as `db_tags` above.
|
||||||
|
|
@ -6,14 +6,14 @@ In the last lesson we created the dragons Fluffy, Cuddly and Smaug and made the
|
||||||
learned a bit about _classes_ in the process. But so far our dragons are short-lived - whenever we `restart`
|
learned a bit about _classes_ in the process. But so far our dragons are short-lived - whenever we `restart`
|
||||||
the server or `quit()` out of python mode they are gone.
|
the server or `quit()` out of python mode they are gone.
|
||||||
|
|
||||||
This is what you should have in `mygame/typeclasses/mobile.py` so far:
|
This is what you should have in `mygame/typeclasses/monsters.py` so far:
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
class Mobile:
|
class Monster:
|
||||||
"""
|
"""
|
||||||
This is a base class for Mobiles.
|
This is a base class for Monsters.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, key):
|
def __init__(self, key):
|
||||||
|
|
@ -23,9 +23,9 @@ class Mobile:
|
||||||
print(f"{self.key} is moving!")
|
print(f"{self.key} is moving!")
|
||||||
|
|
||||||
|
|
||||||
class Dragon(Mobile):
|
class Dragon(Monster):
|
||||||
"""
|
"""
|
||||||
This is a dragon-specific mobile.
|
This is a dragon-specific monster.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def move_around(self):
|
def move_around(self):
|
||||||
|
|
@ -42,8 +42,8 @@ class Dragon(Mobile):
|
||||||
|
|
||||||
## Our first persistent object
|
## Our first persistent object
|
||||||
|
|
||||||
Now we should know enough to understand what is happening in `mygame/typeclasses/objects.py`.
|
At this point we should know enough to understand what is happening in `mygame/typeclasses/objects.py`. Let's
|
||||||
Open it again:
|
open it:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""
|
"""
|
||||||
|
|
@ -68,26 +68,26 @@ change the way it works!
|
||||||
> easiest is to peek at its [API documentation](api:evennia.objects.objects#DefaultObject). The docstring for
|
> easiest is to peek at its [API documentation](api:evennia.objects.objects#DefaultObject). The docstring for
|
||||||
> the `Object` class can also help.
|
> the `Object` class can also help.
|
||||||
|
|
||||||
One thing that Evennia offers and which you don't get with vanilla Python classes is _persistence_. As you've
|
One thing that Evennia classes offers and which you don't get with vanilla Python classes is _persistence_. As
|
||||||
found, Fluffy, Cuddly and Smaug are gone once we reload the server. Let's see if we can fix this.
|
you've found, Fluffy, Cuddly and Smaug are gone once we reload the server. Let's see if we can fix this.
|
||||||
|
|
||||||
Go back to `mygame/typeclasses/mobile.py`. Change it as follows:
|
Go back to `mygame/typeclasses/monsters.py`. Change it as follows:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
from typeclasses.objects import Object
|
from typeclasses.objects import Object
|
||||||
|
|
||||||
class Mobile(Object):
|
class Monster(Object):
|
||||||
"""
|
"""
|
||||||
This is a base class for Mobiles.
|
This is a base class for Monsters.
|
||||||
"""
|
"""
|
||||||
def move_around(self):
|
def move_around(self):
|
||||||
print(f"{self.key} is moving!")
|
print(f"{self.key} is moving!")
|
||||||
|
|
||||||
|
|
||||||
class Dragon(Mobile):
|
class Dragon(Monster):
|
||||||
"""
|
"""
|
||||||
This is a dragon-specific mobile.
|
This is a dragon-specific Monster.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def move_around(self):
|
def move_around(self):
|
||||||
|
|
@ -102,11 +102,11 @@ class Dragon(Mobile):
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Don't forget to save. We removed `Monster.__init__` and made `Mobile` inherit from Evennia's `Object` (which in turn
|
Don't forget to save. We removed `Monster.__init__` and made `Monster` inherit from Evennia's `Object` (which in turn
|
||||||
inherits from Evennia's `DefaultObject`, as we saw). By extension, this means that `Dragon` also inherits
|
inherits from Evennia's `DefaultObject`, as we saw). By extension, this means that `Dragon` also inherits
|
||||||
from `DefaultObject`, just from further away!
|
from `DefaultObject`, just from further away!
|
||||||
|
|
||||||
### Creating by calling the class (less common way)
|
### Making a new object by calling the class
|
||||||
|
|
||||||
First reload the server as usual. We will need to create the dragon a little differently this time:
|
First reload the server as usual. We will need to create the dragon a little differently this time:
|
||||||
|
|
||||||
|
|
@ -119,7 +119,7 @@ First reload the server as usual. We will need to create the dragon a little dif
|
||||||
|
|
||||||
```
|
```
|
||||||
> py
|
> py
|
||||||
> from typeclasses.mymobile import Dragon
|
> from typeclasses.monsters import Dragon
|
||||||
> smaug = Dragon(db_key="Smaug", db_location=here)
|
> smaug = Dragon(db_key="Smaug", db_location=here)
|
||||||
> smaug.save()
|
> smaug.save()
|
||||||
> smaug.move_around()
|
> smaug.move_around()
|
||||||
|
|
@ -138,7 +138,7 @@ You should now see that Smaug _is in the room with you_. Woah!
|
||||||
> reload
|
> reload
|
||||||
> look
|
> look
|
||||||
|
|
||||||
_He's still there_... What we just did is to create a new entry in the database for Smaug. We gave the object
|
_He's still there_... What we just did was to create a new entry in the database for Smaug. We gave the object
|
||||||
its name (key) and set its location to our current location (remember that `here` is just something available
|
its name (key) and set its location to our current location (remember that `here` is just something available
|
||||||
in the `py` command, you can't use it elsewhere).
|
in the `py` command, you can't use it elsewhere).
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ bound Python instances before. But you need to use `db_key` instead of `key` and
|
||||||
remember to call `.save()` afterwards. Evennia has a helper function that is more common to use,
|
remember to call `.save()` afterwards. Evennia has a helper function that is more common to use,
|
||||||
called `create_object`:
|
called `create_object`:
|
||||||
|
|
||||||
> py fluffy = evennia.create_object('typeclases.mymobile.Mobile', key="Fluffy", location=here)
|
> py fluffy = evennia.create_object('typeclases.monster.Monster', key="Fluffy", location=here)
|
||||||
> look
|
> look
|
||||||
|
|
||||||
Boom, Fluffy should now be in the room with you, a little less scary than Smaug. You specify the
|
Boom, Fluffy should now be in the room with you, a little less scary than Smaug. You specify the
|
||||||
|
|
@ -174,130 +174,450 @@ multiple Fluffies we could get the second one with `[1]`.
|
||||||
|
|
||||||
Finally, you can also create a new Dragon using the familiar builder-commands we explored a few lessons ago:
|
Finally, you can also create a new Dragon using the familiar builder-commands we explored a few lessons ago:
|
||||||
|
|
||||||
> create/drop Cuddly:typeclasses.mymobile.Mobile
|
> create/drop Cuddly:typeclasses.monsters.Monster
|
||||||
|
|
||||||
Cuddly is now in the room. After learning about how objects are created you'll realize that all this command really
|
Cuddly is now in the room. After learning about how objects are created you'll realize that all this command really
|
||||||
does is to parse your input, figure out that `/drop` means to "give the object the same location as the caller",
|
does is to parse your input, figure out that `/drop` means to "give the object the same location as the caller",
|
||||||
and then do a call akin to
|
and then do a call akin to
|
||||||
|
|
||||||
evennia.create_object("typeclasses.mymobile.Mobile", key="Cuddly", location=here)
|
evennia.create_object("typeclasses.monsters.Monster", key="Cuddly", location=here)
|
||||||
|
|
||||||
That's pretty much all there is to the mighty `create` command.
|
That's pretty much all there is to the mighty `create` command! The rest is just parsing for the command
|
||||||
|
to understand just what the user wants to create.
|
||||||
|
|
||||||
... And speaking of Commands, we should try to add one of our own next.
|
## Typeclasses
|
||||||
|
|
||||||
|
The `Object` (and `DefafultObject` class we inherited from above is what we refer to as a _Typeclass_. This
|
||||||
|
is an Evennia thing. The instance of a typeclass saves itself to the database when it is created, and after
|
||||||
|
that you can just search for it to get it back. We use the term _typeclass_ or _typeclassed_ to differentiate
|
||||||
|
these types of classes and objects from the normal Python classes, whose instances go away on a reload.
|
||||||
|
|
||||||
|
The number of typeclasses in Evennia are so few they can be learned by heart:
|
||||||
|
|
||||||
|
- `evennia.DefaultObject`: This is the parent of all in-game entities - everything with a location. Evennia makes
|
||||||
|
a few very useful child classes of this class:
|
||||||
|
- `evennia.DefaultCharacter`: The default entity represening a player avatar in-game.
|
||||||
|
- `evennia.DefaultRoom`: A location in the game world.
|
||||||
|
- `evennia.DefaultExit`: A link between locations.
|
||||||
|
- `evennia.DefaultAccount`: The OOC representation of a player, holds password and account info.
|
||||||
|
- `evennia.DefaultChannel`: In-game channels. These could be used for all sorts of in-game communication.
|
||||||
|
- `evennia.DefaultScript`: Out-of-game objects, with no presence in the game world. Anything you want to create that
|
||||||
|
needs to be persistent can be stored with these entities, such as combat state, economic systems or what have you.
|
||||||
|
|
||||||
|
If you take a look in `mygame/typeclasses/` you'll find modules for each of these. Each contains an empty child
|
||||||
|
class ready that already inherits from the right parent, ready for you to modify or build from:
|
||||||
|
|
||||||
# Adding Object Typeclass Tutorial
|
- `mygame/typeclasses/objects.py` has `class Object(DefaultObject)`, a class directly inheriting the basic in-game entity, this
|
||||||
|
works as a base for any object.
|
||||||
|
- `mygame/typeclasses/characters.py` has `class Character(DefaultCharacter)`
|
||||||
|
- `mygame/typeclasses/rooms.py` has `class Room(DefaultRoom)`
|
||||||
|
- `mygame/typeclasses/exits.py` has `class Exit(DefaultExit)`
|
||||||
|
- `mygame/typeclasses/accounts.py` has `class Account(DefaultAccount)`
|
||||||
|
- `mygame/typeclasses/channels.py` has `class Channel(DefaultChannel)`
|
||||||
|
- `mygame/typeclasses/scripts.py` has `class Script(DefaultScript)`
|
||||||
|
|
||||||
Evennia comes with a few very basic classes of in-game entities:
|
> Notice that the classes in `mygame/typeclasses/` are _not inheriting from each other_. For example,
|
||||||
|
> `Character` is inheriting from `evennia.DefaultCharacter` and not from `typeclasses.objects.Object`.
|
||||||
|
> So if you change `Object` you will not cause any change in the `Character` class. If you want that you
|
||||||
|
> can easily just change the child classes to inherit in that way instead; Evennia doesn't care.
|
||||||
|
|
||||||
DefaultObject
|
As seen with our `Dragon` example, you don't _have_ to modify these modules directly. You can just make your
|
||||||
|
|
own modules and import the base class.
|
||||||
DefaultCharacter
|
|
||||||
DefaultRoom
|
|
||||||
DefaultExit
|
|
||||||
DefaultChannel
|
|
||||||
|
|
||||||
When you create a new Evennia game (with for example `evennia --init mygame`) Evennia will
|
### Examining and defaults
|
||||||
automatically create empty child classes `Object`, `Character`, `Room` and `Exit` respectively. They
|
|
||||||
are found `mygame/typeclasses/objects.py`, `mygame/typeclasses/rooms.py` etc.
|
|
||||||
|
|
||||||
> Technically these are all [Typeclassed](../../../Component/Typeclasses), which can be ignored for now. In
|
When you do
|
||||||
> `mygame/typeclasses` are also base typeclasses for out-of-character things, notably
|
|
||||||
> [Channels](../../../Component/Communications), [Accounts](../../../Component/Accounts) and [Scripts](../../../Component/Scripts). We don't cover those in
|
|
||||||
> this tutorial.
|
|
||||||
|
|
||||||
For your own game you will most likely want to expand on these very simple beginnings. It's normal
|
> create/drop giantess:typeclasses.monsters.Monster
|
||||||
to want your Characters to have various attributes, for example. Maybe Rooms should hold extra
|
You create a new Monster: giantess.
|
||||||
information or even *all* Objects in your game should have properties not included in basic Evennia.
|
|
||||||
|
|
||||||
## Change Default Rooms, Exits, Character Typeclass
|
or
|
||||||
|
|
||||||
This is the simplest case.
|
> py evennia.create_object("typeclasses.monsters.Monster", key="Giantess", location=here)
|
||||||
|
|
||||||
The default build commands of a new Evennia game is set up to use the `Room`, `Exit` and `Character`
|
You are specifying exactly which typeclass you want to use to build the Giantess. Let's examine the result:
|
||||||
classes found in the same-named modules under `mygame/typeclasses/`. By default these are empty and
|
|
||||||
just implements the default parents from the Evennia library (`DefaultRoom`etc). Just add the
|
|
||||||
changes you want to these classes and run `@reload` to add your new functionality.
|
|
||||||
|
|
||||||
## Create a new type of object
|
> examine giantess
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Name/key: Giantess (#14)
|
||||||
|
Typeclass: Monster (typeclasses.monsters.Monster)
|
||||||
|
Location: Limbo (#2)
|
||||||
|
Home: Limbo (#2)
|
||||||
|
Permissions: <None>
|
||||||
|
Locks: call:true(); control:id(1) or perm(Admin); delete:id(1) or perm(Admin);
|
||||||
|
drop:holds(); edit:perm(Admin); examine:perm(Builder); get:all();
|
||||||
|
puppet:pperm(Developer); tell:perm(Admin); view:all()
|
||||||
|
Persistent attributes:
|
||||||
|
desc = You see nothing special.
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
Say you want to create a new "Heavy" object-type that characters should not have the ability to pick
|
We used the `examine` command briefly in the [lesson about building in-game](Building-Quickstart). Now these lines
|
||||||
up.
|
may be more useful to us:
|
||||||
|
- **Name/key** - The name of this thing. The value `(#14)` is probably different for you. This is the
|
||||||
|
unique 'primary key' or _dbref_ for this entity in the database.
|
||||||
|
- **Typeclass**: This show the typeclass we specified, and the path to it.
|
||||||
|
- **Location**: We are in Limbo. If you moved elsewhere you'll see that instead. Also the `#dbref` is shown.
|
||||||
|
- **Permissions**: _Permissions_ are like the inverse to _Locks_ - they are like keys to unlock access to other things.
|
||||||
|
The giantess have no such keys (maybe fortunately).
|
||||||
|
- **Locks**: Locks are the inverse of _Permissions_ - specify what criterion _other_ objects must fulfill in order to
|
||||||
|
access the `giantess` object. This uses a very flexible mini-language. For examine, the line `examine:perm(Builders)`
|
||||||
|
is read as "Only those with permission _Builder_ or higher can _examine_ this object". Since we are the superuser
|
||||||
|
we pass (even bypass) such locks with ease.
|
||||||
|
- **Persistent attributes**: This allows for storing arbitrary, persistent data on the typeclassed entity. We'll get
|
||||||
|
to those in the next section.
|
||||||
|
|
||||||
1. Edit `mygame/typeclasses/objects.py` (you could also create a new module there, named something
|
Note how the **Typeclass** line describes exactly where to find the code of this object? This is very useful for
|
||||||
like `heavy.py`, that's up to how you want to organize things).
|
understanding how any object in Evennia works.
|
||||||
1. Create a new class inheriting at any distance from `DefaultObject`. It could look something like
|
|
||||||
this:
|
|
||||||
```python
|
|
||||||
# end of file mygame/typeclasses/objects.py
|
|
||||||
from evennia import DefaultObject
|
|
||||||
|
|
||||||
class Heavy(DefaultObject):
|
What happens if we _don't_ specify the typeclass though?
|
||||||
"Heavy object"
|
|
||||||
def at_object_creation(self):
|
> create/drop box
|
||||||
"Called whenever a new object is created"
|
You create a new Object: box.
|
||||||
# lock the object down by default
|
|
||||||
self.locks.add("get:false()")
|
or
|
||||||
# the default "get" command looks for this Attribute in order
|
|
||||||
# to return a customized error message (we just happen to know
|
> py create.create_object(None, key="box", location=here)
|
||||||
# this, you'd have to look at the code of the 'get' command to
|
|
||||||
# find out).
|
Now check it out:
|
||||||
self.db.get_err_msg = "This is too heavy to pick up."
|
|
||||||
|
> examine box
|
||||||
|
|
||||||
|
You will find that the **Typeclass** line now reads
|
||||||
|
|
||||||
|
Typeclass: Object (typeclasses.objects.Object)
|
||||||
|
|
||||||
|
So when you didn't specify a typeclass, Evennia used a default, more specifically the (so far) empty `Object` class in
|
||||||
|
`mygame/typeclasses/objects.py`. This is usually what you want, especially since you can tweak that class as much
|
||||||
|
as you like.
|
||||||
|
|
||||||
|
But the reason Evennia knows to fall back to this class is not hard-coded - it's a setting. The default is
|
||||||
|
in [evennia/settings_default.py](https://github.com/evennia/evennia/blob/master/evennia/settings_default.py#L465),
|
||||||
|
with the name `BASE_OBJECT_TYPECLASS`, which is set to `typeclasses.objects.Object`.
|
||||||
|
|
||||||
|
```sidebar:: Changing things
|
||||||
|
|
||||||
|
While it's tempting to change folders around to your liking, this can
|
||||||
|
make it harder to follow tutorials and may confuse if
|
||||||
|
you are asking others for help. So don't overdo it unless you really
|
||||||
|
know what you are doing.
|
||||||
```
|
```
|
||||||
1. Once you are done, log into the game with a build-capable account and do `@create/drop
|
|
||||||
rock:objects.Heavy` to drop a new heavy "rock" object in your location. Next try to pick it up
|
|
||||||
(`@quell` yourself first if you are a superuser). If you get errors, look at your log files where
|
|
||||||
you will find the traceback. The most common error is that you have some sort of syntax error in
|
|
||||||
your class.
|
|
||||||
|
|
||||||
Note that the [Locks](../../../Component/Locks) and [Attribute](../../../Component/Attributes) which are set in the typeclass could just
|
So if you wanted the creation commands and methods to default to some other class you could
|
||||||
as well have been set using commands in-game, so this is a *very* simple example.
|
add your own `BASE_OBJECT_TYPECLASS` line to `mygame/server/conf/settings.py`. The same is true for all the other
|
||||||
|
typeclasseses, like characters, rooms and accounts. This way you can change the
|
||||||
|
layout of your game dir considerably if you wanted. You just need to tell Evennia where everything is.
|
||||||
|
|
||||||
## Storing data on initialization
|
## Modifying ourselves
|
||||||
|
|
||||||
The `at_object_creation` is only called once, when the object is first created. This makes it ideal
|
Let's try to modify ourselves a little. Open up `mygame/typeclasses/characters.py`.
|
||||||
for database-bound things like [Attributes](../../../Component/Attributes). But sometimes you want to create temporary
|
|
||||||
properties (things that are not to be stored in the database but still always exist every time the
|
|
||||||
object is created). Such properties can be initialized in the `at_init` method on the object.
|
|
||||||
`at_init` is called every time the object is loaded into memory.
|
|
||||||
|
|
||||||
> Note: It's usually pointless and wasteful to assign database data in `at_init`, since this will
|
|
||||||
> hit the database with the same value over and over. Put those in `at_object_creation` instead.
|
|
||||||
|
|
||||||
You are wise to use `ndb` (non-database Attributes) to store these non-persistent properties, since
|
|
||||||
ndb-properties are protected against being cached out in various ways and also allows you to list
|
|
||||||
them using various in-game tools:
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def at_init(self):
|
"""
|
||||||
self.ndb.counter = 0
|
(module docstring)
|
||||||
self.ndb.mylist = []
|
"""
|
||||||
|
from evennia import DefaultCharacter
|
||||||
|
|
||||||
|
class Character(DefaultCharacter):
|
||||||
|
"""
|
||||||
|
(class docstring)
|
||||||
|
"""
|
||||||
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
> Note: As mentioned in the [Typeclasses](../../../Component/Typeclasses) documentation, `at_init` replaces the use of
|
This looks quite familiar now - an empty class inheriting from the Evennia base typeclass. As you would expect,
|
||||||
> the standard `__init__` method of typeclasses due to how the latter may be called in situations
|
this is also the default typeclass used for creating Characters if you don't specify it. You can verify it:
|
||||||
> other than you'd expect. So use `at_init` where you would normally use `__init__`.
|
|
||||||
|
|
||||||
|
> examine me
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Name/key: YourName (#1)
|
||||||
|
Session id(s): #1
|
||||||
|
Account: YourName
|
||||||
|
Account Perms: <Superuser> (quelled)
|
||||||
|
Typeclass: Character (typeclasses.characters.Character)
|
||||||
|
Location: Limbo (#2)
|
||||||
|
Home: Limbo (#2)
|
||||||
|
Permissions: developer, player
|
||||||
|
Locks: boot:false(); call:false(); control:perm(Developer); delete:false();
|
||||||
|
drop:holds(); edit:false(); examine:perm(Developer); get:false();
|
||||||
|
msg:all(); puppet:false(); tell:perm(Admin); view:all()
|
||||||
|
Stored Cmdset(s):
|
||||||
|
commands.default_cmdsets.CharacterCmdSet [DefaultCharacter] (Union, prio 0)
|
||||||
|
Merged Cmdset(s):
|
||||||
|
...
|
||||||
|
Commands available to YourName (result of Merged CmdSets):
|
||||||
|
...
|
||||||
|
Persistent attributes:
|
||||||
|
desc = This is User #1.
|
||||||
|
prelogout_location = Limbo
|
||||||
|
Non-Persistent attributes:
|
||||||
|
last_cmd = None
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
## Updating existing objects
|
You got a lot longer output this time. You have a lot more going on than a simple Object. Here are some new fields of note:
|
||||||
|
- **Session id(s)**: This identifies the _Session_ (that is, the individual connection to a player's game client).
|
||||||
|
- **Account** shows, well the `Account` object associated with this Character and Session.
|
||||||
|
- **Stored/Merged Cmdsets** and **Commands available** is related to which _Commands_ are stored on you. We will
|
||||||
|
get to them in the [next lesson](Adding-Commands). For now it's enough to know these consitute all the
|
||||||
|
commands available to you at a given moment.
|
||||||
|
- **Non-Persistent attributes** are Attributes that are only stored temporarily and will go away on next reload.
|
||||||
|
|
||||||
If you already have some `Heavy` objects created and you add a new `Attribute` in
|
Look at the **Typeclass** field and you'll find that it points to `typeclasses.character.Character` as expected.
|
||||||
`at_object_creation`, you will find that those existing objects will not have this Attribute. This
|
So if we modify this class we'll also modify ourselves.
|
||||||
is not so strange, since `at_object_creation` is only called once, it will not be called again just
|
|
||||||
because you update it. You need to update existing objects manually.
|
|
||||||
|
|
||||||
If the number of objects is limited, you can use `@typeclass/force/reload objectname` to force a
|
### A method on ourselves
|
||||||
re-load of the `at_object_creation` method (only) on the object. This case is common enough that
|
|
||||||
there is an alias `@update objectname` you can use to get the same effect. If there are multiple
|
Let's try something simple first. Back in `mygame/typeclasses/characters.py`:
|
||||||
objects you can use `@py` to loop over the objects you need:
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
class Character(DefaultCharacter):
|
||||||
|
"""
|
||||||
|
(class docstring)
|
||||||
|
"""
|
||||||
|
|
||||||
|
str = 10
|
||||||
|
dex = 12
|
||||||
|
int = 15
|
||||||
|
|
||||||
|
def get_stats(self):
|
||||||
|
"""
|
||||||
|
Get the main stats of this character
|
||||||
|
"""
|
||||||
|
return self.str, self.dex, self.int
|
||||||
|
|
||||||
```
|
```
|
||||||
@py from typeclasses.objects import Heavy; [obj.at_object_creation() for obj in Heavy.objects.all()]
|
|
||||||
|
> reload
|
||||||
|
> py self.get_stats()
|
||||||
|
(10, 12, 15)
|
||||||
|
|
||||||
|
```sidebar:: Tuples and lists
|
||||||
|
|
||||||
|
- A `list` is written `[a, b, c, d, ...]`. It can be modified after creation.
|
||||||
|
- A `tuple` is written `(a, b, c, ...)`. It cannot be modified once created.
|
||||||
|
```
|
||||||
|
We made a new method, gave it a docstring and had it `return` the RP-esque values we set. It comes back as a
|
||||||
|
_tuple_ `(10, 12, 15)`. To get a specific value you could specify the _index_ of the value you want,
|
||||||
|
starting from zero:
|
||||||
|
|
||||||
|
> py stats = self.get_stats() ; print(f"Strength is {stats[0]}.")
|
||||||
|
Strength is 10.
|
||||||
|
|
||||||
|
### Attributes
|
||||||
|
|
||||||
|
So what happens when we increase our strength? This would be one way:
|
||||||
|
|
||||||
|
> py self.str = self.str + 1
|
||||||
|
> py self.str
|
||||||
|
11
|
||||||
|
|
||||||
|
Here we set the strength equal to its previous value + 1. A shorter way to write this is to use Python's `+=`
|
||||||
|
operator:
|
||||||
|
|
||||||
|
> py self.str += 1
|
||||||
|
> py self.str
|
||||||
|
12
|
||||||
|
> py self.get_stats()
|
||||||
|
(12, 12, 15)
|
||||||
|
|
||||||
|
This looks correct! Try to change the values for dex and int too; it works fine. However:
|
||||||
|
|
||||||
|
> reload
|
||||||
|
> py self.get_stats()
|
||||||
|
(10, 12, 15)
|
||||||
|
|
||||||
|
After a reload all our changes were forgotten. When we change properties like this, it only changes in memory,
|
||||||
|
not in the database (nor do we modify the python module's code). So when we reloaded, the 'fresh' `Character`
|
||||||
|
class was loaded, and it still has the original stats we wrote to it.
|
||||||
|
|
||||||
|
In principle we could change the python code. But we don't want to do that manually every time. And more importantly
|
||||||
|
since we have the stats hardcoded in the class, _every_ character instance in the game will have exactly the
|
||||||
|
same `str`, `dex` and `int` now! This is clearly not what we want.
|
||||||
|
|
||||||
|
Evennia offers a special, persistent type of property for this, called an `Attribute`. Rework your
|
||||||
|
`mygame/typeclasses/characters.py` like this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
class Character(DefaultCharacter):
|
||||||
|
"""
|
||||||
|
(class docstring)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_stats(self):
|
||||||
|
"""
|
||||||
|
Get the main stats of this character
|
||||||
|
"""
|
||||||
|
return self.db.str, self.db.dex, self.db.int
|
||||||
|
```
|
||||||
|
|
||||||
|
```sidebar:: Spaces in Attribute name?
|
||||||
|
|
||||||
|
What if you want spaces in your Attribute name? Or you want to assign the
|
||||||
|
name of the Attribute on-the fly? Then you can use `.attributes.add(name, value)` instead,
|
||||||
|
for example `self.attributes.add("str", 10)`.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
We removed the hard-coded stats and added added `.db` for every stat. The `.db` handler makes the stat
|
||||||
|
into an an Evennia `Attribute`.
|
||||||
|
|
||||||
|
> reload
|
||||||
|
> py self.get_stats()
|
||||||
|
(None, None, None)
|
||||||
|
|
||||||
|
Since we removed the hard-coded values, Evennia don't know what they should be (yet). So all we get back
|
||||||
|
is `None`, which is a Python reserved word to represent nothing, a no-value. This is different from a normal python
|
||||||
|
property:
|
||||||
|
|
||||||
|
> py self.str
|
||||||
|
AttributeError: 'Character' object has no attribute 'str'
|
||||||
|
> py self.db.str
|
||||||
|
(nothing will be displayed, because it's None)
|
||||||
|
|
||||||
|
Trying to get an unknown normal Python property will give an error. Getting an unknown Evennia `Attribute` will
|
||||||
|
never give an error, but only result in `None` being returned. This is often very practical.
|
||||||
|
|
||||||
|
> py self.db.str, self.db.dex, self.db.int = 10, 12, 15
|
||||||
|
> py self.get_stats()
|
||||||
|
(10, 12, 15)
|
||||||
|
> reload
|
||||||
|
> py self.get_stats()
|
||||||
|
(10, 12, 15)
|
||||||
|
|
||||||
|
Now we set the Attributes to the right values. We can see that things work the same as before, also after a
|
||||||
|
server reload. Let's modify the strength:
|
||||||
|
|
||||||
|
> py self.db.str += 2
|
||||||
|
> py self.get_stats()
|
||||||
|
(12, 12, 15)
|
||||||
|
> reload
|
||||||
|
> py self.get_stats()
|
||||||
|
(12, 12, 15)
|
||||||
|
|
||||||
|
Our change now survives a reload since Evennia automatically saves the Attribute to the database for us.
|
||||||
|
|
||||||
|
### Setting things on new Characters
|
||||||
|
|
||||||
|
Things a looking better, but one thing remains strange - the stats start out with a value `None` and we
|
||||||
|
have to manually set them to something reasonable. In a later lesson we will investigate character-creation
|
||||||
|
in more detail. For now, let's give every new character some random stats to start with.
|
||||||
|
|
||||||
|
We want those stats to be set only once, when the object is first created. For the Character, this method
|
||||||
|
is called `at_object_creation`.
|
||||||
|
|
||||||
|
```sidebar:: __init__ vs at_object_creation
|
||||||
|
|
||||||
|
For the `Monster` class we used `__init__` to set up the class. We can't use this
|
||||||
|
for a typeclass because it will be called more than once, at the very least after
|
||||||
|
every reload and maybe more depending on caching. Even if you are familiar with Python,
|
||||||
|
avoid touching `__init__` for typeclasses, the results will not be what you expect.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# up by the other imports
|
||||||
|
import random
|
||||||
|
|
||||||
|
class Character(DefaultCharacter):
|
||||||
|
"""
|
||||||
|
(class docstring)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
self.db.str = random.randint(3, 18)
|
||||||
|
self.db.dex = random.randint(3, 18)
|
||||||
|
self.db.int = random.randint(3, 18)
|
||||||
|
|
||||||
|
def get_stats(self):
|
||||||
|
"""
|
||||||
|
Get the main stats of this character
|
||||||
|
"""
|
||||||
|
return self.db.str, self.db.dex, self.db.int
|
||||||
|
```
|
||||||
|
|
||||||
|
We imported a new module, `random`. This is part of Python's standard library. We used `random.randint` to
|
||||||
|
set a random value from 3 to 18 to each stat. Simple, but for some classical RPGs this is all you need!
|
||||||
|
|
||||||
|
> reload
|
||||||
|
> py self.get_stats()
|
||||||
|
(12, 12, 15)
|
||||||
|
|
||||||
|
Hm, this is the same values we set before. They are not random. The reason for this is of course that, as said,
|
||||||
|
`at_object_creation` only runs _once_, the very first time a character is created. Our character object was already
|
||||||
|
created long before, so it will not be called again.
|
||||||
|
|
||||||
|
It's simple enough to run it manually though:
|
||||||
|
|
||||||
|
> self.at_object_creation()
|
||||||
|
> py self.get_stats()
|
||||||
|
(5, 4, 8)
|
||||||
|
|
||||||
|
Lady luck didn't smile on us for this example; maybe you'll fare better. Evennia has a helper command
|
||||||
|
`update` that re-runs the creation hook and also cleans up any other Attributes not re-created by `at_object_creation`:
|
||||||
|
|
||||||
|
> update self
|
||||||
|
> py self.get_stats()
|
||||||
|
(8, 16, 14)
|
||||||
|
|
||||||
|
### Updating all Characters in a loop
|
||||||
|
|
||||||
|
Needless to say, for your game you are wise to have a feel for what you want to go into the `at_object_creation` hook
|
||||||
|
before you create a lot of objects (characters in this case). But should it come to that you don't want to have to
|
||||||
|
go around and re-run the method on everyone manually. For the Python beginner, doing this will also give a chance to
|
||||||
|
try out Python _loops_. We try them out in multi-line Python mode:
|
||||||
|
|
||||||
|
> py
|
||||||
|
> for a in [1, 2, "foo"]:
|
||||||
|
> print(a)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
foo
|
||||||
|
|
||||||
|
A python _for-loop_ allows us to loop over something. Above, we made a _list_ of two numbers and a string. In
|
||||||
|
every iteration of the loop, the variable `a` becomes one element in turn, and we print that.
|
||||||
|
|
||||||
|
For our list, we want to loop over all Characters, and want to call `.at_object_creation` on each. This is how
|
||||||
|
this is done (still in python multi-line mode):
|
||||||
|
|
||||||
|
> from typeclasses.characters import Character
|
||||||
|
> for char in Character.objects.all()
|
||||||
|
> char.at_object_creation()
|
||||||
|
|
||||||
|
```sidebar:: Database queries
|
||||||
|
|
||||||
|
`Character.objects.all()` is an example of a database query expressed in Python. This will be converted
|
||||||
|
into a database query under the hood. This syntax is part of
|
||||||
|
`Django's query language <https://docs.djangoproject.com/en/3.0/topics/db/queries/>`_. You don't need to
|
||||||
|
know Django to use Evennia, but if you ever need more specific database queries, this is always available
|
||||||
|
when you need it.
|
||||||
|
|
||||||
|
```
|
||||||
|
We import the `Character` class and then we use `.objects.all()` to get all `Character` instances. Simplified,
|
||||||
|
`.objects` is a resource from which one can _query_ for all `Characters`. Using `.all()` gets us a listing
|
||||||
|
of all of them that we then immediately loop over. Boom, we just updated all Characters, including ourselves:
|
||||||
|
|
||||||
|
> quit()
|
||||||
|
Closing the Python console.
|
||||||
|
> self.get_stats()
|
||||||
|
(3, 18, 10)
|
||||||
|
|
||||||
|
## Extra Credits
|
||||||
|
|
||||||
|
This principle is the same for other typeclasses. So using the tools explored in this lesson, try to expand
|
||||||
|
the default room with an `is_dark` flag. It can be either `True` or `False`.
|
||||||
|
Have all new rooms start with `is_dark = False` and make it so that once you change it, it survives a reload.
|
||||||
|
Oh, and if you created any other rooms before, make sure they get the new flag too!
|
||||||
|
|
||||||
|
## Conclusions
|
||||||
|
|
||||||
|
In this lesson we created database-persistent dragons by having their classes inherit from one `Object`, one
|
||||||
|
of Evennia's _typeclasses_. We explored where Evennia looks for typeclasses if we don't specify the path
|
||||||
|
explicitly. We then modified ourselves - via the `Character` class - to give us some simple RPG stats. This
|
||||||
|
led to the need to use Evennia's _Attributes_, settable via `.db` and to use a for-loop to update ourselves.
|
||||||
|
|
||||||
|
Typeclasses are a fundamental part of Evennia and we will see a lot of more uses of them in the course of
|
||||||
|
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](Python-classes-and-objects) | [next lesson](Adding-Commands)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# More about Commands
|
# More about Commands
|
||||||
|
|
||||||
[prev lesson](Adding-Commands) | [next lesson](Learning-Typeclasses)
|
[prev lesson](Adding-Commands) | [next lesson](Evennia-API-Overview)
|
||||||
|
|
||||||
In this lesson we learn some basics about parsing the input of Commands. We will
|
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.
|
also learn how to add, modify and extend Evennia's default commands.
|
||||||
|
|
@ -497,6 +497,8 @@ 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
|
the future! We also made a functional sword. Finally we got into how to add to, extend and replace a default
|
||||||
command on ourselves.
|
command on ourselves.
|
||||||
|
|
||||||
Let's explore 'ourselves' and other 'things' in the game next.
|
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](Learning-Typeclasses)
|
|
||||||
|
[prev lesson](Adding-Commands) | [next lesson](Evennia-API-Overview)
|
||||||
|
|
|
||||||
|
|
@ -165,14 +165,14 @@ things to understand before you can use Evennia efficiently.
|
||||||
### Classes and instances
|
### Classes and instances
|
||||||
|
|
||||||
A 'class' can be seen as a 'template' for a 'type' of object. The class describes the basic functionality
|
A 'class' can be seen as a 'template' for a 'type' of object. The class describes the basic functionality
|
||||||
of everyone of that class. For example, we could have a class `Mobile` which has resources for moving itself
|
of everyone of that class. For example, we could have a class `Monster` which has resources for moving itself
|
||||||
from room to room.
|
from room to room.
|
||||||
|
|
||||||
Open a new file `mygame/typeclasses/mymobile.py`. Add the following simple class:
|
Open a new file `mygame/typeclasses/monsters.py`. Add the following simple class:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
class Mobile:
|
class Monster:
|
||||||
|
|
||||||
key = "Monster"
|
key = "Monster"
|
||||||
|
|
||||||
|
|
@ -181,7 +181,7 @@ class Mobile:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Above we have defined a `Mobile` class with one variable `key` (that is, the name) and one
|
Above we have defined a `Monster` class with one variable `key` (that is, the name) and one
|
||||||
_method_ on it. A method is like a function except it sits "on" the class. It also always has
|
_method_ on it. A method is like a function except it sits "on" the class. It also always has
|
||||||
at least one argument (almost always written as `self` although you could in principle use
|
at least one argument (almost always written as `self` although you could in principle use
|
||||||
another name), which is a reference back to itself. So when we print `self.key` we are referring
|
another name), which is a reference back to itself. So when we print `self.key` we are referring
|
||||||
|
|
@ -194,20 +194,20 @@ back to the `key` on the class.
|
||||||
|
|
||||||
```
|
```
|
||||||
A class is just a template. Before it can be used, we must create an _instance_ of the class. If
|
A class is just a template. Before it can be used, we must create an _instance_ of the class. If
|
||||||
`Mobile` is a class, then an instance is Fluffy, the individual red dragon. You instantiate
|
`Monster` is a class, then an instance is Fluffy, the individual red dragon. You instantiate
|
||||||
by _calling_ the class, much like you would a function:
|
by _calling_ the class, much like you would a function:
|
||||||
|
|
||||||
fluffy = Mobile()
|
fluffy = Monster()
|
||||||
|
|
||||||
Let's try it in-game (we use multi-line mode, it's easier)
|
Let's try it in-game (we use multi-line mode, it's easier)
|
||||||
|
|
||||||
> py
|
> py
|
||||||
> from typeclasses.mymobile import Mobile
|
> from typeclasses.monsters import Monster
|
||||||
> fluffy = Mobile()
|
> fluffy = Monster()
|
||||||
> fluffy.move_around()
|
> fluffy.move_around()
|
||||||
Monster is moving!
|
Monster is moving!
|
||||||
|
|
||||||
We created an _instance_ of `Mobile`, which we stored in the variable `fluffy`. We then
|
We created an _instance_ of `Monster`, which we stored in the variable `fluffy`. We then
|
||||||
called the `move_around` method on fluffy to get the printout.
|
called the `move_around` method on fluffy to get the printout.
|
||||||
|
|
||||||
> Note how we _didn't_ call the method as `fluffy.move_around(self)`. While the `self` has to be
|
> Note how we _didn't_ call the method as `fluffy.move_around(self)`. While the `self` has to be
|
||||||
|
|
@ -216,7 +216,7 @@ called the `move_around` method on fluffy to get the printout.
|
||||||
|
|
||||||
Let's create the sibling of Fluffy, Cuddly:
|
Let's create the sibling of Fluffy, Cuddly:
|
||||||
|
|
||||||
> cuddly = Mobile()
|
> cuddly = Monster()
|
||||||
> cuddly.move_around()
|
> cuddly.move_around()
|
||||||
Monster is moving!
|
Monster is moving!
|
||||||
|
|
||||||
|
|
@ -228,7 +228,7 @@ Let's make the class a little more flexible:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
class Mobile:
|
class Monster:
|
||||||
|
|
||||||
def __init__(self, key):
|
def __init__(self, key):
|
||||||
self.key = key
|
self.key = key
|
||||||
|
|
@ -239,7 +239,7 @@ class Mobile:
|
||||||
```
|
```
|
||||||
|
|
||||||
The `__init__` is a special method that Python recognizes. If given, this handles extra arguments
|
The `__init__` is a special method that Python recognizes. If given, this handles extra arguments
|
||||||
when you instantiate a new Mobile. We have it add an argument `key` that we store on `self`.
|
when you instantiate a new Monster. We have it add an argument `key` that we store on `self`.
|
||||||
|
|
||||||
Now, for Evennia to see this code change, we need to reload the server. You can either do it this
|
Now, for Evennia to see this code change, we need to reload the server. You can either do it this
|
||||||
way:
|
way:
|
||||||
|
|
@ -262,8 +262,8 @@ Or you can use a separate terminal and restart from outside the game:
|
||||||
Either way you'll need to go into `py` again:
|
Either way you'll need to go into `py` again:
|
||||||
|
|
||||||
> py
|
> py
|
||||||
> from typeclasses.mymobile import Mobile
|
> from typeclasses.monsters import Monster
|
||||||
fluffy = Mobile("Fluffy")
|
fluffy = Monster("Fluffy")
|
||||||
fluffy.move_around()
|
fluffy.move_around()
|
||||||
Fluffy is moving!
|
Fluffy is moving!
|
||||||
|
|
||||||
|
|
@ -276,7 +276,7 @@ So far all we've seen a class do is to behave our first `hello_world` function b
|
||||||
could just have made a function:
|
could just have made a function:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def mobile_move_around(key):
|
def monster_move_around(key):
|
||||||
print(f"{key} is moving!")
|
print(f"{key} is moving!")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -306,13 +306,13 @@ objects in turn:
|
||||||
Classes can _inherit_ from each other. A "child" class will inherit everything from its "parent" class. But if
|
Classes can _inherit_ from each other. A "child" class will inherit everything from its "parent" class. But if
|
||||||
the child adds something with the same name as its parent, it will _override_ whatever it got from its parent.
|
the child adds something with the same name as its parent, it will _override_ whatever it got from its parent.
|
||||||
|
|
||||||
Let's expand `mygame/typeclasses/mymobile.py` with another class:
|
Let's expand `mygame/typeclasses/monsters.py` with another class:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
class Mobile:
|
class Monster:
|
||||||
"""
|
"""
|
||||||
This is a base class for Mobiles.
|
This is a base class for Monster.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, key):
|
def __init__(self, key):
|
||||||
|
|
@ -322,9 +322,9 @@ class Mobile:
|
||||||
print(f"{self.key} is moving!")
|
print(f"{self.key} is moving!")
|
||||||
|
|
||||||
|
|
||||||
class Dragon(Mobile):
|
class Dragon(Monster):
|
||||||
"""
|
"""
|
||||||
This is a dragon-specific mobile.
|
This is a dragon-specific monster.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def move_around(self):
|
def move_around(self):
|
||||||
|
|
@ -341,7 +341,7 @@ class Dragon(Mobile):
|
||||||
We added some docstrings for clarity. It's always a good idea to add doc strings; you can do so also for methods,
|
We added some docstrings for clarity. It's always a good idea to add doc strings; you can do so also for methods,
|
||||||
as exemplified for the new `firebreath` method.
|
as exemplified for the new `firebreath` method.
|
||||||
|
|
||||||
We created the new class `Dragon` but we also specified that `Mobile` is the _parent_ of `Dragon` but adding
|
We created the new class `Dragon` but we also specified that `Monster` is the _parent_ of `Dragon` but adding
|
||||||
the parent in parenthesis. `class Classname(Parent)` is the way to do this.
|
the parent in parenthesis. `class Classname(Parent)` is the way to do this.
|
||||||
|
|
||||||
```sidebar:: Multi-inheritance
|
```sidebar:: Multi-inheritance
|
||||||
|
|
@ -355,7 +355,7 @@ the parent in parenthesis. `class Classname(Parent)` is the way to do this.
|
||||||
Let's try out our new class. First `reload` the server and the do
|
Let's try out our new class. First `reload` the server and the do
|
||||||
|
|
||||||
> py
|
> py
|
||||||
> from typeclasses.mobile import Dragon
|
> from typeclasses.monsters import Dragon
|
||||||
> smaug = Dragon("Smaug")
|
> smaug = Dragon("Smaug")
|
||||||
> smaug.move_around()
|
> smaug.move_around()
|
||||||
Smaug flies through the air high above!
|
Smaug flies through the air high above!
|
||||||
|
|
@ -391,7 +391,7 @@ case, we will call `Monster.move_around` first, before doing our own thing.
|
||||||
Now `reload` the server and then:
|
Now `reload` the server and then:
|
||||||
|
|
||||||
> py
|
> py
|
||||||
> from typeclasses.mobile import Dragon
|
> from typeclasses.monsters import Dragon
|
||||||
> smaug = Dragon("Smaug")
|
> smaug = Dragon("Smaug")
|
||||||
> smaug.move_around()
|
> smaug.move_around()
|
||||||
Smaug is moving!
|
Smaug is moving!
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@
|
||||||
- [Howto/Starting/Parsing command arguments, theory and best practices](Howto/Starting/Parsing-command-arguments,-theory-and-best-practices)
|
- [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/Adding Commands](Howto/Starting/Part1/Adding-Commands)
|
||||||
- [Howto/Starting/Part1/Building Quickstart](Howto/Starting/Part1/Building-Quickstart)
|
- [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/Gamedir Overview](Howto/Starting/Part1/Gamedir-Overview)
|
- [Howto/Starting/Part1/Gamedir Overview](Howto/Starting/Part1/Gamedir-Overview)
|
||||||
- [Howto/Starting/Part1/Learning Typeclasses](Howto/Starting/Part1/Learning-Typeclasses)
|
- [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/More on Commands](Howto/Starting/Part1/More-on-Commands)
|
||||||
|
|
@ -113,7 +114,6 @@
|
||||||
- [Howto/Starting/Starting Part4](Howto/Starting/Starting-Part4)
|
- [Howto/Starting/Starting Part4](Howto/Starting/Starting-Part4)
|
||||||
- [Howto/Starting/Starting Part5](Howto/Starting/Starting-Part5)
|
- [Howto/Starting/Starting Part5](Howto/Starting/Starting-Part5)
|
||||||
- [Howto/Starting/Turn based Combat System](Howto/Starting/Turn-based-Combat-System)
|
- [Howto/Starting/Turn based Combat System](Howto/Starting/Turn-based-Combat-System)
|
||||||
- [Howto/Starting/Tutorial Searching For Objects](Howto/Starting/Tutorial-Searching-For-Objects)
|
|
||||||
- [Howto/Starting/Tutorial for basic MUSH like game](Howto/Starting/Tutorial-for-basic-MUSH-like-game)
|
- [Howto/Starting/Tutorial for basic MUSH like game](Howto/Starting/Tutorial-for-basic-MUSH-like-game)
|
||||||
- [Howto/Starting/Web Tutorial](Howto/Starting/Web-Tutorial)
|
- [Howto/Starting/Web Tutorial](Howto/Starting/Web-Tutorial)
|
||||||
- [Howto/Tutorial Aggressive NPCs](Howto/Tutorial-Aggressive-NPCs)
|
- [Howto/Tutorial Aggressive NPCs](Howto/Tutorial-Aggressive-NPCs)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue