mirror of
https://github.com/evennia/evennia.git
synced 2026-03-20 23:06:31 +01:00
Finished typeclass docs
This commit is contained in:
parent
216485cb26
commit
b96b43aa0b
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
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
big cmdset.
|
||||
|
||||
Command-Sets are then associated with objects. Doing so makes the commands in that cmdset available
|
||||
to the object. So, to summarize:
|
||||
Command-Sets are then associated with objects, for example with your Character. Doing so makes the
|
||||
commands in that cmdset available to the object. So, to summarize:
|
||||
|
||||
- Commands are classes
|
||||
- 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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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).
|
||||
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?
|
||||
|
|
@ -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
|
||||
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
|
||||
- [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
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
|
|
@ -50,7 +114,7 @@ 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
|
||||
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.
|
||||
|
||||
|
|
@ -64,7 +128,7 @@ 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
|
||||
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
|
||||
|
|
@ -133,7 +197,7 @@ class CmdListHangouts(default_cmds.MuxCommand):
|
|||
", ".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".
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
|
@ -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`
|
||||
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
|
||||
|
||||
class Mobile:
|
||||
class Monster:
|
||||
"""
|
||||
This is a base class for Mobiles.
|
||||
This is a base class for Monsters.
|
||||
"""
|
||||
|
||||
def __init__(self, key):
|
||||
|
|
@ -23,9 +23,9 @@ class Mobile:
|
|||
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):
|
||||
|
|
@ -42,8 +42,8 @@ class Dragon(Mobile):
|
|||
|
||||
## Our first persistent object
|
||||
|
||||
Now we should know enough to understand what is happening in `mygame/typeclasses/objects.py`.
|
||||
Open it again:
|
||||
At this point we should know enough to understand what is happening in `mygame/typeclasses/objects.py`. Let's
|
||||
open it:
|
||||
|
||||
```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
|
||||
> 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
|
||||
found, Fluffy, Cuddly and Smaug are gone once we reload the server. Let's see if we can fix this.
|
||||
One thing that Evennia classes offers and which you don't get with vanilla Python classes is _persistence_. As
|
||||
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
|
||||
|
||||
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):
|
||||
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):
|
||||
|
|
@ -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
|
||||
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:
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ First reload the server as usual. We will need to create the dragon a little dif
|
|||
|
||||
```
|
||||
> py
|
||||
> from typeclasses.mymobile import Dragon
|
||||
> from typeclasses.monsters import Dragon
|
||||
> smaug = Dragon(db_key="Smaug", db_location=here)
|
||||
> smaug.save()
|
||||
> smaug.move_around()
|
||||
|
|
@ -138,7 +138,7 @@ You should now see that Smaug _is in the room with you_. Woah!
|
|||
> reload
|
||||
> 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
|
||||
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,
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
> 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
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
|
||||
|
||||
# Adding Object Typeclass Tutorial
|
||||
|
||||
Evennia comes with a few very basic classes of in-game entities:
|
||||
|
||||
DefaultObject
|
||||
|
|
||||
DefaultCharacter
|
||||
DefaultRoom
|
||||
DefaultExit
|
||||
DefaultChannel
|
||||
|
||||
When you create a new Evennia game (with for example `evennia --init mygame`) Evennia will
|
||||
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
|
||||
> `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
|
||||
to want your Characters to have various attributes, for example. Maybe Rooms should hold extra
|
||||
information or even *all* Objects in your game should have properties not included in basic Evennia.
|
||||
|
||||
## Change Default Rooms, Exits, Character Typeclass
|
||||
|
||||
This is the simplest case.
|
||||
|
||||
The default build commands of a new Evennia game is set up to use the `Room`, `Exit` and `Character`
|
||||
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
|
||||
|
||||
Say you want to create a new "Heavy" object-type that characters should not have the ability to pick
|
||||
up.
|
||||
|
||||
1. Edit `mygame/typeclasses/objects.py` (you could also create a new module there, named something
|
||||
like `heavy.py`, that's up to how you want to organize things).
|
||||
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
|
||||
- `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.
|
||||
|
||||
class Heavy(DefaultObject):
|
||||
"Heavy object"
|
||||
def at_object_creation(self):
|
||||
"Called whenever a new object is created"
|
||||
# lock the object down by default
|
||||
self.locks.add("get:false()")
|
||||
# the default "get" command looks for this Attribute in order
|
||||
# to return a customized error message (we just happen to know
|
||||
# this, you'd have to look at the code of the 'get' command to
|
||||
# find out).
|
||||
self.db.get_err_msg = "This is too heavy to pick up."
|
||||
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:
|
||||
|
||||
- `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)`
|
||||
|
||||
> 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.
|
||||
|
||||
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.
|
||||
|
||||
### Examining and defaults
|
||||
|
||||
When you do
|
||||
|
||||
> create/drop giantess:typeclasses.monsters.Monster
|
||||
You create a new Monster: giantess.
|
||||
|
||||
or
|
||||
|
||||
> py evennia.create_object("typeclasses.monsters.Monster", key="Giantess", location=here)
|
||||
|
||||
You are specifying exactly which typeclass you want to use to build the Giantess. Let's examine the result:
|
||||
|
||||
> 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.
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
We used the `examine` command briefly in the [lesson about building in-game](Building-Quickstart). Now these lines
|
||||
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.
|
||||
|
||||
Note how the **Typeclass** line describes exactly where to find the code of this object? This is very useful for
|
||||
understanding how any object in Evennia works.
|
||||
|
||||
What happens if we _don't_ specify the typeclass though?
|
||||
|
||||
> create/drop box
|
||||
You create a new Object: box.
|
||||
|
||||
or
|
||||
|
||||
> py create.create_object(None, key="box", location=here)
|
||||
|
||||
Now check it out:
|
||||
|
||||
> 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
|
||||
as well have been set using commands in-game, so this is a *very* simple example.
|
||||
So if you wanted the creation commands and methods to default to some other class you could
|
||||
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.
|
||||
|
||||
## Modifying ourselves
|
||||
|
||||
## Storing data on initialization
|
||||
|
||||
The `at_object_creation` is only called once, when the object is first created. This makes it ideal
|
||||
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:
|
||||
Let's try to modify ourselves a little. Open up `mygame/typeclasses/characters.py`.
|
||||
|
||||
```python
|
||||
def at_init(self):
|
||||
self.ndb.counter = 0
|
||||
self.ndb.mylist = []
|
||||
"""
|
||||
(module docstring)
|
||||
"""
|
||||
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
|
||||
> the standard `__init__` method of typeclasses due to how the latter may be called in situations
|
||||
> other than you'd expect. So use `at_init` where you would normally use `__init__`.
|
||||
This looks quite familiar now - an empty class inheriting from the Evennia base typeclass. As you would expect,
|
||||
this is also the default typeclass used for creating Characters if you don't specify it. You can verify it:
|
||||
|
||||
> 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
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
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.
|
||||
|
||||
## Updating existing objects
|
||||
Look at the **Typeclass** field and you'll find that it points to `typeclasses.character.Character` as expected.
|
||||
So if we modify this class we'll also modify ourselves.
|
||||
|
||||
If you already have some `Heavy` objects created and you add a new `Attribute` in
|
||||
`at_object_creation`, you will find that those existing objects will not have this Attribute. This
|
||||
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.
|
||||
### A method on ourselves
|
||||
|
||||
If the number of objects is limited, you can use `@typeclass/force/reload objectname` to force a
|
||||
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
|
||||
objects you can use `@py` to loop over the objects you need:
|
||||
Let's try something simple first. Back in `mygame/typeclasses/characters.py`:
|
||||
|
||||
```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)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 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
|
||||
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
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
class Mobile:
|
||||
class 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
|
||||
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
|
||||
|
|
@ -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
|
||||
`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:
|
||||
|
||||
fluffy = Mobile()
|
||||
fluffy = Monster()
|
||||
|
||||
Let's try it in-game (we use multi-line mode, it's easier)
|
||||
|
||||
> py
|
||||
> from typeclasses.mymobile import Mobile
|
||||
> fluffy = Mobile()
|
||||
> from typeclasses.monsters import Monster
|
||||
> fluffy = Monster()
|
||||
> fluffy.move_around()
|
||||
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.
|
||||
|
||||
> 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:
|
||||
|
||||
> cuddly = Mobile()
|
||||
> cuddly = Monster()
|
||||
> cuddly.move_around()
|
||||
Monster is moving!
|
||||
|
||||
|
|
@ -228,7 +228,7 @@ Let's make the class a little more flexible:
|
|||
|
||||
```python
|
||||
|
||||
class Mobile:
|
||||
class Monster:
|
||||
|
||||
def __init__(self, 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
|
||||
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
|
||||
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:
|
||||
|
||||
> py
|
||||
> from typeclasses.mymobile import Mobile
|
||||
fluffy = Mobile("Fluffy")
|
||||
> from typeclasses.monsters import Monster
|
||||
fluffy = Monster("Fluffy")
|
||||
fluffy.move_around()
|
||||
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:
|
||||
|
||||
```python
|
||||
def mobile_move_around(key):
|
||||
def monster_move_around(key):
|
||||
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
|
||||
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
|
||||
|
||||
class Mobile:
|
||||
class Monster:
|
||||
"""
|
||||
This is a base class for Mobiles.
|
||||
This is a base class for Monster.
|
||||
"""
|
||||
|
||||
def __init__(self, key):
|
||||
|
|
@ -322,9 +322,9 @@ class Mobile:
|
|||
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):
|
||||
|
|
@ -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,
|
||||
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.
|
||||
|
||||
```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
|
||||
|
||||
> py
|
||||
> from typeclasses.mobile import Dragon
|
||||
> from typeclasses.monsters import Dragon
|
||||
> smaug = Dragon("Smaug")
|
||||
> smaug.move_around()
|
||||
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:
|
||||
|
||||
> py
|
||||
> from typeclasses.mobile import Dragon
|
||||
> from typeclasses.monsters import Dragon
|
||||
> smaug = Dragon("Smaug")
|
||||
> smaug.move_around()
|
||||
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/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/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)
|
||||
|
|
@ -113,7 +114,6 @@
|
|||
- [Howto/Starting/Starting Part4](Howto/Starting/Starting-Part4)
|
||||
- [Howto/Starting/Starting Part5](Howto/Starting/Starting-Part5)
|
||||
- [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/Web Tutorial](Howto/Starting/Web-Tutorial)
|
||||
- [Howto/Tutorial Aggressive NPCs](Howto/Tutorial-Aggressive-NPCs)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue