Change to MyST parser

This commit is contained in:
Griatch 2021-10-21 21:04:14 +02:00
parent 53106e1dba
commit a51e4af609
443 changed files with 4925 additions and 3524 deletions

View file

@ -7,7 +7,7 @@ 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
```{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
@ -181,7 +181,7 @@ class CmdEcho(Command):
First we added a docstring. This is always a good thing to do in general, but for a Command class, it will also
automatically become the in-game help entry! Next we add the `func` method. It has one active line where it
makes use of some of those variables we found the Command offers to us. If you did the
[basic Python tutorial](./Python-basic-introduction), you will recognize `.msg` - this will send a message
[basic Python tutorial](./Python-basic-introduction.md), you will recognize `.msg` - this will send a message
to the object it is attached to us - in this case `self.caller`, that is, us. We grab `self.args` and includes
that in the message.
@ -306,7 +306,7 @@ A lot of things to dissect here:
`self.args.strip()` over and over, we store the stripped version
in a _local variable_ `args`. Note that we don't modify `self.args` by doing this, `self.args` will still
have the whitespace and is not the same as `args` in this example.
```sidebar:: if-statements
```{sidebar} if-statements
The full form of the if statement is
@ -354,7 +354,7 @@ class MyCmdSet(CmdSet):
```
```sidebar:: Errors in your code
```{sidebar} Errors in your code
With longer code snippets to try, it gets more and more likely you'll
make an error and get a `traceback` when you reload. This will either appear

View file

@ -1,17 +1,17 @@
# Using the game and building stuff
In this lesson we will test out what we can do in-game out-of-the-box. Evennia ships with
[around 90 default commands](api:evennia.commands.default#modules), and while you can override those as you please,
In this lesson we will test out what we can do in-game out-of-the-box. Evennia ships with
[around 90 default commands](../../../Components/Default-Commands.md), and while you can override those as you please,
they can be quite useful.
Connect and log into your new game and you will end up in the "Limbo" location. This
Connect and log into your new game and you will end up in the "Limbo" location. This
is the only room in the game at this point. Let's explore the commands a little.
The default commands has syntax [similar to MUX](../../../Concepts/Using-MUX-as-a-Standard):
The default commands has syntax [similar to MUX](../../../Concepts/Using-MUX-as-a-Standard.md):
command[/switch/switch...] [arguments ...]
An example would be
An example would be
create/drop box
@ -21,32 +21,32 @@ or more inputs to the commands. It's common to use an equal sign (`=`) when assi
an object.
> Are you used to commands starting with @, like @create? That will work too. Evennia simply ignores
> the preceeding @.
> the preceeding @.
## Getting help
help
Will give you a list of all commands available to you. Use
help
Will give you a list of all commands available to you. Use
help <commandname>
to see the in-game help for that command.
to see the in-game help for that command.
## Looking around
The most common comman is
The most common comman is
look
This will show you the description of the current location. `l` is an alias.
This will show you the description of the current location. `l` is an alias.
When targeting objects in commands you have two special labels you can use, `here` for the current
room or `me`/`self` to point back to yourself. So
room or `me`/`self` to point back to yourself. So
look me
will give you your own description. `look here` is, in this case, the same as plain `look`.
will give you your own description. `look here` is, in this case, the same as plain `look`.
## Stepping Down From Godhood
@ -81,9 +81,9 @@ This created a new 'box' (of the default object type) in your inventory. Use the
name box = very large box;box;very;crate
```warning:: MUD clients and semi-colon
```{warning} MUD clients and semi-colon
Some traditional MUD clients use the semi-colon `;` to separate client inputs. If so,
Some traditional MUD clients use the semi-colon `;` to separate client inputs. If so,
the above line will give an error. You need to change your client to use another command-separator
or to put it in 'verbatim' mode. If you still have trouble, use the Evennia web client instead.
@ -99,8 +99,8 @@ used the `alias` command.
We are currently carrying the box. Let's drop it (there is also a short cut to create and drop in
one go by using the `/drop` switch, for example `create/drop box`).
drop box
drop box
Hey presto - there it is on the ground, in all its normality.
@ -109,7 +109,7 @@ Hey presto - there it is on the ground, in all its normality.
This will show some technical details about the box object. For now we will ignore what this
information means.
Try to `look` at the box to see the (default) description.
Try to `look` at the box to see the (default) description.
look box
You see nothing special.
@ -125,20 +125,20 @@ dropped in the room, then try this:
lock box = get:false()
Locks represent a rather [big topic](../../../Components/Locks), but for now that will do what we want. This will lock
Locks represent a rather [big topic](../../../Components/Locks.md), but for now that will do what we want. This will lock
the box so noone can lift it. The exception is superusers, they override all locks and will pick it
up anyway. Make sure you are quelling your superuser powers and try to get the box now:
> get box
You can't get that.
Think thís default error message looks dull? The `get` command looks for an [Attribute](../../../Components/Attributes)
Think thís default error message looks dull? The `get` command looks for an [Attribute](../../../Components/Attributes.md)
named `get_err_msg` for returning a nicer error message (we just happen to know this, you would need
to peek into the
[code](https://github.com/evennia/evennia/blob/master/evennia/commands/default/general.py#L235) for
the `get` command to find out.). You set attributes using the `set` command:
set box/get_err_msg = It's way too heavy for you to lift.
set box/get_err_msg = It's way too heavy for you to lift.
Try to get it now and you should see a nicer error message echoed back to you. To see what this
message string is in the future, you can use 'examine.'
@ -149,12 +149,12 @@ Examine will return the value of attributes, including color codes. `examine her
the raw description of your current room (including color codes), so that you can copy-and-paste to
set its description to something else.
You create new Commands (or modify existing ones) in Python outside the game. We will get to that
later, in the [Commands tutorial](./Adding-Commands).
You create new Commands (or modify existing ones) in Python outside the game. We will get to that
later, in the [Commands tutorial](./Adding-Commands.md).
## Get a Personality
[Scripts](../../../Components/Scripts) are powerful out-of-character objects useful for many "under the hood" things.
[Scripts](../../../Components/Scripts.md) are powerful out-of-character objects useful for many "under the hood" things.
One of their optional abilities is to do things on a timer. To try out a first script, let's put one
on ourselves. There is an example script in `evennia/contrib/tutorial_examples/bodyfunctions.py`
that is called `BodyFunctions`. To add this to us we will use the `script` command:
@ -162,16 +162,16 @@ that is called `BodyFunctions`. To add this to us we will use the `script` comma
script self = tutorial_examples.bodyfunctions.BodyFunctions
This string will tell Evennia to dig up the Python code at the place we indicate. It already knows
to look in the `contrib/` folder, so we don't have to give the full path.
to look in the `contrib/` folder, so we don't have to give the full path.
> Note also how we use `.` instead of `/` (or `\` on Windows). This is a so-called "Python path". In a Python-path,
> Note also how we use `.` instead of `/` (or `\` on Windows). This is a so-called "Python path". In a Python-path,
> you separate the parts of the path with `.` and skip the `.py` file-ending. Importantly, it also allows you to point to
Python code _inside_ files, like the `BodyFunctions` class inside `bodyfunctions.py` (we'll get to classes later).
These "Python-paths" are used extensively throughout Evennia.
Python code _inside_ files, like the `BodyFunctions` class inside `bodyfunctions.py` (we'll get to classes later).
These "Python-paths" are used extensively throughout Evennia.
Wait a while and you will notice yourself starting making random observations ...
script self
script self
This will show details about scripts on yourself (also `examine` works). You will see how long it is
until it "fires" next. Don't be alarmed if nothing happens when the countdown reaches zero - this
@ -183,14 +183,14 @@ When you are tired of your character's "insights", kill the script with
script/stop self = tutorial_examples.bodyfunctions.BodyFunctions
You create your own scripts in Python, outside the game; the path you give to `script` is literally
the Python path to your script file. The [Scripts](../../../Components/Scripts) page explains more details.
the Python path to your script file. The [Scripts](../../../Components/Scripts.md) page explains more details.
## Pushing Your Buttons
If we get back to the box we made, there is only so much fun you can have with it at this point. It's
just a dumb generic object. If you renamed it to `stone` and changed its description, noone would be
the wiser. However, with the combined use of custom [Typeclasses](../../../Components/Typeclasses), [Scripts](../../../Components/Scripts)
and object-based [Commands](../../../Components/Commands), you could expand it and other items to be as unique, complex
the wiser. However, with the combined use of custom [Typeclasses](../../../Components/Typeclasses.md), [Scripts](../../../Components/Scripts.md)
and object-based [Commands](../../../Components/Commands.md), you could expand it and other items to be as unique, complex
and interactive as you want.
Let's take an example. So far we have only created objects that use the default object typeclass
@ -206,18 +206,18 @@ The same way we did with the Script Earler, we specify a "Python-path" to the Py
to use for creating the object. There you go - one red button.
The RedButton is an example object intended to show off a few of Evennia's features. You will find
that the [Typeclass](../../../Components/Typeclasses) and [Commands](../../../Components/Commands) controlling it are
inside [evennia/contrib/tutorial_examples](api:evennia.contrib.tutorial_examples)
that the [Typeclass](../../../Components/Typeclasses.md) and [Commands](../../../Components/Commands.md) controlling it are
inside [evennia/contrib/tutorial_examples](../../../api/evennia.contrib.tutorial_examples.md)
If you wait for a while (make sure you dropped it!) the button will blink invitingly.
If you wait for a while (make sure you dropped it!) the button will blink invitingly.
Why don't you try to push it ...?
Why don't you try to push it ...?
Surely a big red button is meant to be pushed.
Surely a big red button is meant to be pushed.
You know you want to.
```warning:: Don't press the invitingly blinking red button.
```{warning} Don't press the invitingly blinking red button.
```
## Making Yourself a House
@ -242,14 +242,14 @@ also up/down and in/out). It's called `tunnel`:
This will create a new room "cliff" with an exit "southwest" leading there and a path "northeast"
leading back from the cliff to your current location.
You can create new exits from where you are, using the `open` command:
You can create new exits from where you are, using the `open` command:
open north;n = house
This opens an exit `north` (with an alias `n`) to the previously created room `house`.
If you have many rooms named `house` you will get a list of matches and have to select which one you
want to link to.
want to link to.
Follow the north exit to your 'house' or `teleport` to it:
@ -274,7 +274,7 @@ _large box_ to our house.
very large box is leaving Limbo, heading for house.
Teleported very large box -> house.
We can still find the box by using find:
We can still find the box by using find:
find box
One Match(#1-#8):
@ -291,15 +291,15 @@ We are getting tired of the box. Let's destroy it.
destroy box
It will ask you for confirmation. Once you give it, the box will be gone.
It will ask you for confirmation. Once you give it, the box will be gone.
You can destroy many objects in one go by giving a comma-separated list of objects (or a range
of #dbrefs, if they are not in the same location) to the command.
## Adding a Help Entry
The Command-help is something you modify in Python code. We'll get to that when we get to how to
add Commands. But you can also add regular help entries, for example to explain something about
The Command-help is something you modify in Python code. We'll get to that when we get to how to
add Commands. But you can also add regular help entries, for example to explain something about
the history of your game world:
sethelp/add History = At the dawn of time ...
@ -308,5 +308,5 @@ You will now find your new `History` entry in the `help` list and read your help
## Adding a World
After this brief introduction to building and using in-game commands you may be ready to see a more fleshed-out
After this brief introduction to building and using in-game commands you may be ready to see a more fleshed-out
example. Evennia comes with a tutorial world for you to explore. We will try that out in the next section.

View file

@ -20,7 +20,7 @@ Given the path to a Typeclass, there are three ways to create an instance of it:
This is the recommended way if you are trying to create things in Python. The first argument can either be
the class _or_ the python-path to the typeclass, like `"path.to.SomeTypeClass"`. It can also be `None` in which
case the Evennia default will be used. While all the creation methods
are available on `evennia`, they are actually implemented in [evennia/utils/create.py](api:evennia.utils.create).
are available on `evennia`, they are actually implemented in [evennia/utils/create.py](../../../api/evennia.utils.create.md).
- Finally, you can create objects using an in-game command, such as
create/drop obj:path.to.SomeTypeClass

View file

@ -1,88 +1,88 @@
# Django Database queries
```important:: More advanced lesson!
Learning about Django's queryset language is very useful once you start doing more advanced things
in Evennia. But it's not strictly needed out the box and can be a little overwhelming for a first
reading. So if you are new to Python and Evennia, feel free to just skim this lesson and refer
```{important} More advanced lesson!
Learning about Django's queryset language is very useful once you start doing more advanced things
in Evennia. But it's not strictly needed out the box and can be a little overwhelming for a first
reading. So if you are new to Python and Evennia, feel free to just skim this lesson and refer
back to it later when you've gained more experience.
```
The search functions and methods we used in the previous lesson are enough for most cases.
But sometimes you need to be more specific:
The search functions and methods we used in the previous lesson are enough for most cases.
But sometimes you need to be more specific:
- You want to find all `Characters` ...
- ... who are in Rooms tagged as `moonlit` ...
- ... who are in Rooms tagged as `moonlit` ...
- ... _and_ who has the Attribute `lycantrophy` with a level higher than 2 ...
- ... because they'll should immediately transform to werewolves!
- ... because they'll should immediately transform to werewolves!
In principle you could achieve this with the existing search functions combined with a lot of loops
and if statements. But for something non-standard like this, querying the database directly will be
In principle you could achieve this with the existing search functions combined with a lot of loops
and if statements. But for something non-standard like this, querying the database directly will be
much more efficient.
Evennia uses [Django](https://www.djangoproject.com/) to handle its connection to the database.
A [django queryset](https://docs.djangoproject.com/en/3.0/ref/models/querysets/) represents
a database query. One can add querysets together to build ever-more complicated queries. Only when
you are trying to use the results of the queryset will it actually call the database.
A [django queryset](https://docs.djangoproject.com/en/3.0/ref/models/querysets/) represents
a database query. One can add querysets together to build ever-more complicated queries. Only when
you are trying to use the results of the queryset will it actually call the database.
The normal way to build a queryset is to define what class of entity you want to search by getting its
`.objects` resource, and then call various methods on that. We've seen this one before:
The normal way to build a queryset is to define what class of entity you want to search by getting its
`.objects` resource, and then call various methods on that. We've seen this one before:
all_weapons = Weapon.objects.all()
This is now a queryset representing all instances of `Weapon`. If `Weapon` had a subclass `Cannon` and we
This is now a queryset representing all instances of `Weapon`. If `Weapon` had a subclass `Cannon` and we
only wanted the cannons, we would do
all_cannons = Cannon.objects.all()
Note that `Weapon` and `Cannon` are different typeclasses. You won't find any `Cannon` instances in
the `all_weapon` result above, confusing as that may sound. To get instances of a Typeclass _and_ the
Note that `Weapon` and `Cannon` are different typeclasses. You won't find any `Cannon` instances in
the `all_weapon` result above, confusing as that may sound. To get instances of a Typeclass _and_ the
instances of all its children classes you need to use `_family`:
```sidebar:: _family
```{sidebar} _family
The all_family, filter_family etc is an Evennia-specific
The all_family, filter_family etc is an Evennia-specific
thing. It's not part of regular Django.
```
really_all_weapons = Weapon.objects.all_family()
This result now contains both `Weapon` and `Cannon` instances.
To limit your search by other criteria than the Typeclass you need to use `.filter`
(or `.filter_family`) instead:
This result now contains both `Weapon` and `Cannon` instances.
To limit your search by other criteria than the Typeclass you need to use `.filter`
(or `.filter_family`) instead:
roses = Flower.objects.filter(db_key="rose")
This is a queryset representing all objects having a `db_key` equal to `"rose"`.
This is a queryset representing all objects having a `db_key` equal to `"rose"`.
Since this is a queryset you can keep adding to it; this will act as an `AND` condition.
local_roses = roses.filter(db_location=myroom)
We could also have written this in one statement:
We could also have written this in one statement:
local_roses = Flower.objects.filter(db_key="rose", db_location=myroom)
We can also `.exclude` something from results
local_non_red_roses = local_roses.exclude(db_key="red_rose")
Only until we actually try to examine the result will the database be called. Here it's called when we
try to loop over the queryset:
Only until we actually try to examine the result will the database be called. Here it's called when we
try to loop over the queryset:
for rose in local_non_red_roses:
print(rose)
From now on, the queryset is _evaluated_ and we can't keep adding more queries to it - we'd need to
From now on, the queryset is _evaluated_ and we can't keep adding more queries to it - we'd need to
create a new queryset if we wanted to find some other result. Other ways to evaluate the queryset is to
print it, convert it to a list with `list()` and otherwise try to access its results.
Note how we use `db_key` and `db_location`. This is the actual names of these database fields. By convention
Evennia uses `db_` in front of every database field. When you use the normal Evennia search helpers and objects
you can skip the `db_` but here we are calling the database directly and need to use the 'real' names.
you can skip the `db_` but here we are calling the database directly and need to use the 'real' names.
Here are the most commonly used methods to use with the `objects` managers:
Here are the most commonly used methods to use with the `objects` managers:
- `filter` - query for a listing of objects based on search criteria. Gives empty queryset if none
were found.
@ -91,71 +91,71 @@ found.
- `all` - get all instances of the particular type.
- `filter_family` - like `filter`, but search all sub classes as well.
- `get_family` - like `get`, but search all sub classes as well.
- `all_family` - like `all`, but return entities of all subclasses as well.
- `all_family` - like `all`, but return entities of all subclasses as well.
> All of Evennia search functions use querysets under the hood. The `evennia.search_*` functions actually
> All of Evennia search functions use querysets under the hood. The `evennia.search_*` functions actually
> return querysets, which means you could in principle keep adding queries to their results as well.
### Queryset field lookups
## Queryset field lookups
Above we found roses with exactly the `db_key` `"rose"`. This is an _exact_ match that is _case sensitive_,
so it would not find `"Rose"`.
Above we found roses with exactly the `db_key` `"rose"`. This is an _exact_ match that is _case sensitive_,
so it would not find `"Rose"`.
# this is case-sensitive and the same as =
roses = Flower.objects.filter(db_key__exact="rose"
# the i means it's case-insensitive
roses = Flower.objects.filter(db_key__iexact="rose")
The Django field query language uses `__` in the same way as Python uses `.` to access resources. This
is because `.` is not allowed in a function keyword.
The Django field query language uses `__` in the same way as Python uses `.` to access resources. This
is because `.` is not allowed in a function keyword.
roses = Flower.objects.filter(db_key__icontains="rose")
This will find all flowers whose name contains the string `"rose"`, like `"roses"`, `"wild rose"` etc. The
`i` in the beginning makes the search case-insensitive. Other useful variations to use
are `__istartswith` and `__iendswith`. You can also use `__gt`, `__ge` for "greater-than"/"greater-or-equal-than"
This will find all flowers whose name contains the string `"rose"`, like `"roses"`, `"wild rose"` etc. The
`i` in the beginning makes the search case-insensitive. Other useful variations to use
are `__istartswith` and `__iendswith`. You can also use `__gt`, `__ge` for "greater-than"/"greater-or-equal-than"
comparisons (same for `__lt` and `__le`). There is also `__in`:
swords = Weapons.objects.filter(db_key__in=("rapier", "two-hander", "shortsword"))
One also uses `__` to access foreign objects like Tags. Let's for example assume this is how we identify mages:
char.tags.add("mage", category="profession")
Now, in this case we have an Evennia helper to do this search:
Now, in this case we have an Evennia helper to do this search:
mages = evennia.search_tags("mage", category="profession")
But this will find all Objects with this tag+category. Maybe you are only looking for Vampire mages:
sparkly_mages = Vampire.objects.filter(db_tags__db_key="mage", db_tags__db_category="profession")
This looks at the `db_tags` field on the `Vampire` and filters on the values of each tag's
This looks at the `db_tags` field on the `Vampire` and filters on the values of each tag's
`db_key` and `db_category` together.
For more field lookups, see the
For more field lookups, see the
[django docs](https://docs.djangoproject.com/en/3.0/ref/models/querysets/#field-lookups) on the subject.
### Get that werewolf ...
## Get that werewolf ...
Let's see if we can make a query for the werewolves in the moonlight we mentioned at the beginning
of this section.
Let's see if we can make a query for the werewolves in the moonlight we mentioned at the beginning
of this section.
Firstly, we make ourselves and our current location match the criteria, so we can test:
Firstly, we make ourselves and our current location match the criteria, so we can test:
> py here.tags.add("moonlit")
> py me.db.lycantrophy = 3
This is an example of a more complex query. We'll consider it an example of what is
This is an example of a more complex query. We'll consider it an example of what is
possible.
```sidebar:: Line breaks
```{sidebar} Line breaks
Note the way of writing this code. It would have been very hard to read if we just wrote it in
Note the way of writing this code. It would have been very hard to read if we just wrote it in
one long line. But since we wrapped it in `(...)` we can spread it out over multiple lines
without worrying about line breaks!
without worrying about line breaks!
```
```python
@ -172,30 +172,30 @@ will_transform = (
- **Line 3** - We want to find `Character`s, so we access `.objects` on the `Character` typeclass.
- **Line 4** - We start to filter ...
- **Line 5**
- **Line 5**
- ... by accessing the `db_location` field (usually this is a Room)
- ... and on that location, we get the value of `db_tags` (this is a _many-to-many_ database field
that we can treat like an object for this purpose; it references all Tags on the location)
that we can treat like an object for this purpose; it references all Tags on the location)
- ... and from those `Tags`, we looking for `Tags` whose `db_key` is "monlit" (non-case sensitive).
- **Line 6** - ... We also want only Characters with `Attributes` whose `db_key` is exactly `"lycantrophy"`
- **Line 7** - ... at the same time as the `Attribute`'s `db_value` is greater-than 2.
Running this query makes our newly lycantrrophic Character appear in `will_transform`. Success!
- **Line 7** - ... at the same time as the `Attribute`'s `db_value` is greater-than 2.
> Don't confuse database fields with [Attributes](../../../Components/Attributes) you set via `obj.db.attr = 'foo'` or
Running this query makes our newly lycantrrophic Character appear in `will_transform`. Success!
> Don't confuse database fields with [Attributes](../../../Components/Attributes.md) 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.
separate fields *on* that object like `db_key` or `db_location` are.
### Complex queries
## Complex queries
All examples so far used `AND` relations. The arguments to `.filter` are added together with `AND`
("we want tag room to be "monlit" _and_ lycantrhopy be > 2").
For queries using `OR` and `NOT` we need Django's
[Q object](https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q-objects). It is
imported from Django directly:
For queries using `OR` and `NOT` we need Django's
[Q object](https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q-objects). It is
imported from Django directly:
from django.db.models import Q
from django.db.models import Q
The `Q` is an object that is created with the same arguments as `.filter`, for example
@ -205,28 +205,28 @@ You can then use this `Q` instance as argument in a `filter`:
q1 = Q(db_key="foo")
Character.objects.filter(q1)
The useful thing about `Q` is that these objects can be chained together with special symbols (bit operators):
`|` for `OR` and `&` for `AND`. A tilde `~` in front negates the expression inside the `Q` and thus
works like `NOT`.
The useful thing about `Q` is that these objects can be chained together with special symbols (bit operators):
`|` for `OR` and `&` for `AND`. A tilde `~` in front negates the expression inside the `Q` and thus
works like `NOT`.
q1 = Q(db_key="Dalton")
q2 = Q(db_location=prison)
Character.objects.filter(q1 | ~q2)
Would get all Characters that are either named "Dalton" _or_ which is _not_ in prison. The result is a mix
of Daltons and non-prisoners.
of Daltons and non-prisoners.
Let us expand our original werewolf query. Not only do we want to find all Characters in a moonlit room
with a certain level of `lycanthrophy`. Now we also want the full moon to immediately transform people who were
with a certain level of `lycanthrophy`. Now we also want the full moon to immediately transform people who were
recently bitten, even if their `lycantrophy` level is not yet high enough (more dramatic this way!). Let's say there is
a Tag "recently_bitten" that controls this.
This is how we'd change our query:
This is how we'd change our query:
```python
from django.db.models import Q
from django.db.models import Q
will_transform = (
Character.objects
@ -244,7 +244,7 @@ will_transform = (
That's quite compact. It may be easier to see what's going on if written this way:
```python
from django.db.models import Q
from django.db.models import Q
q_moonlit = Q(db_location__db_tags__db_key__iexact="moonlit")
q_lycantropic = Q(db_attributes__db_key="lycantrophy", db_attributes__db_value__gt=2)
@ -257,7 +257,7 @@ will_transform = (
)
```
```sidebar:: SQL
```{sidebar} SQL
These Python structures are internally converted to SQL, the native language of the database.
If you are familiar with SQL, these are many-to-many tables joined with `LEFT OUTER JOIN`,
@ -266,31 +266,31 @@ will_transform = (
```
This reads as "Find all Characters in a moonlit room that either has the Attribute `lycantrophy` higher
than two _or_ which has the Tag `recently_bitten`". With an OR-query like this it's possible to find the
same Character via different paths, so we add `.distinct()` at the end. This makes sure that there is only
than two _or_ which has the Tag `recently_bitten`". With an OR-query like this it's possible to find the
same Character via different paths, so we add `.distinct()` at the end. This makes sure that there is only
one instance of each Character in the result.
### Annotations
## Annotations
What if we wanted to filter on some condition that isn't represented easily by a field on the
object? Maybe we want to find rooms only containing five or more objects?
We *could* do it like this (don't actually do it this way!):
We *could* do it like this (don't actually do it this way!):
```python
from typeclasses.rooms import Room
all_rooms = Rooms.objects.all()
all_rooms = Rooms.objects.all()
rooms_with_five_objects = []
for room in all_rooms:
for room in all_rooms:
if len(room.contents) >= 5:
rooms_with_five_objects.append(room)
```
Above we get all rooms and then use `list.append()` to keep adding the right rooms
to an ever-growing list. This is _not_ a good idea, once your database grows this will
be unnecessarily computing-intensive. The database is much more suitable for this.
Above we get all rooms and then use `list.append()` to keep adding the right rooms
to an ever-growing list. This is _not_ a good idea, once your database grows this will
be unnecessarily computing-intensive. The database is much more suitable for this.
_Annotations_ allow you to set a 'variable' inside the query that you can
then access from other parts of the query. Let's do the same example as before directly in the database:
@ -307,10 +307,10 @@ rooms = (
)
```
`Count` is a Django class for counting the number of things in the database.
`Count` is a Django class for counting the number of things in the database.
Here we first create an annotation `num_objects` of type `Count`. It creates an in-database function
that will count the number of results inside the database.
that will count the number of results inside the database.
> Note the use of `location_set` in that `Count`. The `*_set` is a back-reference automatically created by
Django. In this case it allows you to find all objects that *has the current object as location*.
@ -319,13 +319,13 @@ Next we filter on this annotation, using the name `num_objects` as something we
use `num_objects__gte=5` which means that `num_objects` should be greater than 5. This is a little
harder to get one's head around but much more efficient than lopping over all objects in Python.
### F-objects
## F-objects
What if we wanted to compare two dynamic parameters against one another in a query? For example, what if
instead of having 5 or more objects, we only wanted objects that had a bigger inventory than they had
tags (silly example, but ...)? This can be with Django's
instead of having 5 or more objects, we only wanted objects that had a bigger inventory than they had
tags (silly example, but ...)? This can be with Django's
[F objects](https://docs.djangoproject.com/en/1.11/ref/models/expressions/#f-expressions).
So-called F expressions allow you to do a query that looks at a value of each object in the database.
So-called F expressions allow you to do a query that looks at a value of each object in the database.
```python
from django.db.models import Count, F
@ -334,24 +334,24 @@ from typeclasses.rooms import Room
result = (
Room.objects
.annotate(
num_objects=Count('locations_set'),
num_objects=Count('locations_set'),
num_tags=Count('db_tags'))
.filter(num_objects__gt=F('num_tags'))
)
```
Here we used `.annotate` to create two in-query 'variables' `num_objects` and `num_tags`. We then
Here we used `.annotate` to create two in-query 'variables' `num_objects` and `num_tags`. We then
directly use these results in the filter. Using `F()` allows for also the right-hand-side of the filter
condition to be calculated on the fly, completely within the database.
### Grouping and returning only certain properties
## Grouping and returning only certain properties
Suppose you used tags to mark someone belonging to an organization. Now you want to make a list and
need to get the membership count of every organization all at once.
need to get the membership count of every organization all at once.
The `.annotate`, `.values_list`, and `.order_by` queryset methods are useful for this. Normally when
you run a `.filter`, what you get back is a bunch of full typeclass instances, like roses or swords.
Using `.values_list` you can instead choose to only get back certain properties on objects.
The `.annotate`, `.values_list`, and `.order_by` queryset methods are useful for this. Normally when
you run a `.filter`, what you get back is a bunch of full typeclass instances, like roses or swords.
Using `.values_list` you can instead choose to only get back certain properties on objects.
The `.order_by` method finally allows for sorting the results according to some criterion:
@ -372,7 +372,7 @@ Here we fetch all Characters who ...
- ... along the way we count how many different Characters (each `id` is unique) we find for each organization
and store it in a 'variable' `tagcount` using `.annotate` and `Count`
- ... we use this count to sort the result in descending order of `tagcount` (descending because there is a minus sign,
default is increasing order but we want the most popular organization to be first).
default is increasing order but we want the most popular organization to be first).
- ... and finally we make sure to only return exactly the properties we want, namely the name of the organization tag
and how many matches we found for that organization.
@ -380,18 +380,18 @@ The result queryset will be a list of tuples ordered in descending order by the
in a format like the following:
```
[
('Griatch's poets society', 3872),
("Chainsol's Ainneve Testers", 2076),
('Griatch's poets society', 3872),
("Chainsol's Ainneve Testers", 2076),
("Blaufeuer's Whitespace Fixers", 1903),
("Volund's Bikeshed Design Crew", 1764),
("Tehom's Glorious Misanthropes", 1763)
]
```
## Conclusions
## Conclusions
We have covered a lot of ground in this lesson and covered several more complex topics. Knowing how to
query using Django is a powerful skill to have.
We have covered a lot of ground in this lesson and covered several more complex topics. Knowing how to
query using Django is a powerful skill to have.
This concludes the first part of the Evennia starting tutorial - "What we have". Now we have a good foundation
to understand how to plan what our tutorial game will be about.

View file

@ -1,126 +1,123 @@
# Overview of the Evennia library
```sidebar:: API
```{sidebar} API
API stands for `Application Programming Interface`, a description for how to access
the resources of a program or library.
```
A good place to start exploring Evennia is the [Evenia-API frontpage](../../../Evennia-API).
This page sums up the main components of Evennia with a short description of each. Try clicking through
A good place to start exploring Evennia is the [Evenia-API frontpage](../../../Evennia-API.md).
This page sums up the main components of Evennia with a short description of each. Try clicking through
to a few entries - once you get deep enough you'll see full descriptions
of each component along with their documentation. You can also click `[source]` to see the full Python source
for each thing.
of each component along with their documentation. You can also click `[source]` to see the full Python source
for each thing.
You can also browse [the evennia repository on github](https://github.com/evennia/evennia). This is exactly
what you can download from us. The github repo is also searchable.
Finally, you can clone the evennia repo to your own computer and read the sources locally. This is necessary
if you want to help with Evennia's development itself. See the
[extended install instructions](../../../Setup/Extended-Installation) if you want to do this.
### Where is it?
If Evennia is installed, you can import from it simply with
what you can download from us. The github repo is also searchable.
Finally, you can clone the evennia repo to your own computer and read the sources locally. This is necessary
if you want to help with Evennia's development itself. See the
[extended install instructions](../../../Setup/Extended-Installation.md) if you want to do this.
## Where is it?
If Evennia is installed, you can import from it simply with
import evennia
from evennia import some_module
from evennia.some_module.other_module import SomeClass
and so on.
from evennia.some_module.other_module import SomeClass
and so on.
If you installed Evennia with `pip install`, the library folder will be installed deep inside your Python
installation. If you cloned the repo there will be a folder `evennia` on your hard drive there.
installation. If you cloned the repo there will be a folder `evennia` on your hard drive there.
If you cloned the repo or read the code on `github` you'll find this being the outermost structure:
evennia/
bin/
evennia/
bin/
CHANGELOG.md
...
...
docs/
evennia/
evennia/
This outer layer is for Evennia's installation and package distribution. That internal folder `evennia/evennia/` is
This outer layer is for Evennia's installation and package distribution. That internal folder `evennia/evennia/` is
the _actual_ library, the thing covered by the API auto-docs and what you get when you do `import evennia`.
> The `evennia/docs/` folder contains the sources for this documentation. See
> [contributing to the docs](../../../Contributing-Docs) if you want to learn more about how this works.
> The `evennia/docs/` folder contains the sources for this documentation. See
> [contributing to the docs](../../../Contributing-Docs.md) if you want to learn more about how this works.
This the the structure of the Evennia library:
- evennia
- [`__init__.py`](../../../Evennia-API#shortcuts) - The "flat API" of Evennia resides here.
- [`settings_default.py`](../../../Components/Server-Conf#Settings-file) - Root settings of Evennia. Copy settings
- [`__init__.py`](../../../Evennia-API.md#shortcuts) - The "flat API" of Evennia resides here.
- [`settings_default.py`](../../../Setup/Server-Conf.md#settings-file) - Root settings of Evennia. Copy settings
from here to `mygame/server/settings.py` file.
- [`commands/`](../../../Components/Commands) - The command parser and handler.
- `default/` - The [default commands](api:evennia.commands.default#modules) and cmdsets.
- [`comms/`](../../../Components/Communications) - Systems for communicating in-game.
- [`commands/`](../../../Components/Commands.md) - The command parser and handler.
- `default/` - The [default commands](../../../Components/Default-Commands.md) and cmdsets.
- [`comms/`](../../../Components/Communications.md) - Systems for communicating in-game.
- `contrib/` - Optional plugins too game-specific for core Evennia.
- `game_template/` - Copied to become the "game directory" when using `evennia --init`.
- [`help/`](../../../Components/Help-System) - Handles the storage and creation of help entries.
- `locale/` - Language files ([i18n](../../../Concepts/Internationalization)).
- [`locks/`](../../../Components/Locks) - Lock system for restricting access to in-game entities.
- [`objects/`](../../../Components/Objects) - In-game entities (all types of items and Characters).
- [`prototypes/`](../../../Components/Prototypes) - Object Prototype/spawning system and OLC menu
- [`accounts/`](../../../Components/Accounts) - Out-of-game Session-controlled entities (accounts, bots etc)
- [`scripts/`](../../../Components/Scripts) - Out-of-game entities equivalence to Objects, also with timer support.
- [`server/`](../../../Components/Portal-And-Server) - Core server code and Session handling.
- `game_template/` - Copied to become the "game directory" when using `evennia --init`.
- [`help/`](../../../Components/Help-System.md) - Handles the storage and creation of help entries.
- `locale/` - Language files ([i18n](../../../Concepts/Internationalization.md)).
- [`locks/`](../../../Components/Locks.md) - Lock system for restricting access to in-game entities.
- [`objects/`](../../../Components/Objects.md) - In-game entities (all types of items and Characters).
- [`prototypes/`](../../../Components/Prototypes.md) - Object Prototype/spawning system and OLC menu
- [`accounts/`](../../../Components/Accounts.md) - Out-of-game Session-controlled entities (accounts, bots etc)
- [`scripts/`](../../../Components/Scripts.md) - Out-of-game entities equivalence to Objects, also with timer support.
- [`server/`](../../../Components/Portal-And-Server.md) - Core server code and Session handling.
- `portal/` - Portal proxy and connection protocols.
- [`typeclasses/`](../../../Components/Typeclasses) - Abstract classes for the typeclass storage and database system.
- [`utils/`](../../../Components/Coding-Utils) - Various miscellaneous useful coding resources.
- [`web/`](../../../Concepts/Web-Features) - Web resources and webserver. Partly copied into game directory on initialization.
- [`typeclasses/`](../../../Components/Typeclasses.md) - Abstract classes for the typeclass storage and database system.
- [`utils/`](../../../Components/Coding-Utils.md) - Various miscellaneous useful coding resources.
- [`web/`](../../../Concepts/Web-Features.md) - Web resources and webserver. Partly copied into game directory on initialization.
```sidebar:: __init__.py
```{sidebar} __init__.py
The `__init__.py` file is a special Python filename used to represent a Python 'package'.
When you import `evennia` on its own, you import this file. When you do `evennia.foo` Python will
first look for a property `.foo` in `__init__.py` and then for a module or folder of that name
in the same location.
first look for a property `.foo` in `__init__.py` and then for a module or folder of that name
in the same location.
```
While all the actual Evennia code is found in the various folders, the `__init__.py` represents the entire
While all the actual Evennia code is found in the various folders, the `__init__.py` represents the entire
package `evennia`. It contains "shortcuts" to code that is actually located elsewhere. Most of these shortcuts
are listed if you [scroll down a bit](../../../Evennia-API) on the Evennia-API page.
are listed if you [scroll down a bit](../../../Evennia-API.md) on the Evennia-API page.
## An example of exploring the library
In the previous lesson we took a brief look at `mygame/typeclasses/objects` as an example of a Python module. Let's
open it again. Inside is the `Object` class, which inherits from `DefaultObject`.
open it again. Inside is the `Object` class, which inherits from `DefaultObject`.
Near the top of the module is this line:
from evennia import DefaultObject
We want to figure out just what this DefaultObject offers. Since this is imported directly from `evennia`, we
are actually importing from `evennia/__init__.py`.
We want to figure out just what this DefaultObject offers. Since this is imported directly from `evennia`, we
are actually importing from `evennia/__init__.py`.
[Look at Line 189](evennia/__init__.py#L189) of `evennia/__init__.py` and you'll find this line:
[Look at Line 159](github:evennia/__init__.py#159) of `evennia/__init__.py` and you'll find this line:
from .objects.objects import DefaultObject
from .objects.objects import DefaultObject
```sidebar:: Relative and absolute imports
```{sidebar} Relative and absolute imports
The first full-stop in `from .objects.objects ...` means that
The first full-stop in `from .objects.objects ...` means that
we are importing from the current location. This is called a `relative import`.
By comparison, `from evennia.objects.objects` is an `absolute import`. In this particular
case, the two would give the same result.
case, the two would give the same result.
```
> You can also look at [the right section of the API frontpage](../../../Evennia-API#typeclasses) and click through
> You can also look at [the right section of the API frontpage](../../../Evennia-API.md#typeclasses) and click through
> to the code that way.
The fact that `DefaultObject` is imported into `__init__.py` here is what makes it possible to also import
The fact that `DefaultObject` is imported into `__init__.py` here is what makes it possible to also import
it as `from evennia import DefaultObject` even though the code for the class is not actually here.
So to find the code for `DefaultObject` we need to look in `evennia/objects/objects.py`. Here's how
to look it up in the docs:
1. Open the [API frontpage](../../../Evennia-API)
2. Locate the link to [evennia.objects](api:evennia.objects) and click on it.
3. Click through to [evennia.objects.objects](api:evennia.objects.objects).
4. You are now in the python module. Scroll down (or search in your web browser) to find the `DefaultObject` class.
5. You can now read what this does and what methods are on it. If you want to see the full source, click the
\[[source](src:evennia.objects.objects#DefaultObject)\] link.
So to find the code for `DefaultObject` we need to look in `evennia/objects/objects.py`. Here's how
to look it up in the docs:
1. Open the [API frontpage](../../../Evennia-API.md)
2. Locate the link to [evennia.objects.objects](evennia.objects.objects) and click on it.
3 You are now in the python module. Scroll down (or search in your web browser) to find the `DefaultObject` class.
4 You can now read what this does and what methods are on it. If you want to see the full source, click the
\[source\] link next to it.

View file

@ -15,7 +15,7 @@ Like everywhere in the docs we'll assume it's called `mygame`.
You may have noticed when we were building things in-game that we would often refer to code through
"python paths", such as
```sidebar:: Python-paths
```{sidebar} Python-paths
A 'python path' uses '.' instead of '/' or '`\\`' and
skips the `.py` ending of files. It can also point to
@ -57,10 +57,10 @@ and how you point to it correctly.
## commands/
The `commands/` folder holds Python modules related to creating and extending the [Commands](../../../Components/Commands)
The `commands/` folder holds Python modules related to creating and extending the [Commands](../../../Components/Commands.md)
of Evennia. These manifest in game like the server understanding input like `look` or `dig`.
```sidebar:: Classes
```{sidebar} Classes
A `class` is template for creating object-instances of a particular type
in Python. We will explain classes in more detail in the next
@ -149,28 +149,28 @@ knows where they are and will read them to configure itself at startup.
### typeclasses/
The [Typeclasses](../../../Components/Typeclasses) of Evennia are Evennia-specific Python classes whose instances save themselves
The [Typeclasses](../../../Components/Typeclasses.md) of Evennia are Evennia-specific Python classes whose instances save themselves
to the database. This allows a Character to remain in the same place and your updated strength stat to still
be the same after a server reboot.
- [accounts.py](github:evennia/game_template/typeclasses/accounts.py) (Python-path: `typeclasses.accounts`) - An
[Account](../../../Components/Accounts) represents the player connecting to the game. It holds information like email,
[Account](../../../Components/Accounts.md) represents the player connecting to the game. It holds information like email,
password and other out-of-character details.
- [channels.py](github:evennia/game_template/typeclasses/channels.py) (Python-path: `typeclasses.channels`) -
[Channels](../../../Components/Channels) are used to manage in-game communication between players.
[Channels](../../../Components/Channels.md) are used to manage in-game communication between players.
- [objects.py](github:evennia/game_template/typeclasses/objects.py) (Python-path: `typeclasses.objects`) -
[Objects](../../../Components/Objects) represent all things having a location within the game world.
[Objects](../../../Components/Objects.md) represent all things having a location within the game world.
- [characters.py](github:evennia/game_template/typeclasses/characters.py) (Python-path: `typeclasses.characters`) -
The [Character](../../../Components/Objects#Characers) is a subclass of Objects, controlled by Accounts - they are the player's
The [Character](../../../Components/Objects.md#characters) is a subclass of Objects, controlled by Accounts - they are the player's
avatars in the game world.
- [rooms.py](github:evennia/game_template/typeclasses/rooms.py) (Python-path: `typeclasses.rooms`) - A
[Room](../../../Components/Objects#Room) is also a subclass of Object; describing discrete locations. While the traditional
[Room](../../../Components/Objects.md#rooms) is also a subclass of Object; describing discrete locations. While the traditional
term is 'room', such a location can be anything and on any scale that fits your game, from a forest glade,
an entire planet or an actual dungeon room.
- [exits.py](github:evennia/game_template/typeclasses/exits.py) (Python-path: `typeclasses.exits`) -
[Exits](../../../Components/Objects#Exit) is another subclass of Object. Exits link one Room to another.
[Exits](../../../Components/Objects.md#exits) is another subclass of Object. Exits link one Room to another.
- [scripts.py](github:evennia/game_template/typeclasses/scripts.py) (Python-path: `typeclasses.scripts`) -
[Scripts](../../../Components/Scripts) are 'out-of-character' objects. They have no location in-game and can serve as basis for
[Scripts](../../../Components/Scripts.md) are 'out-of-character' objects. They have no location in-game and can serve as basis for
anything that needs database persistence, such as combat, weather, or economic systems. They also
have the ability to execute code repeatedly, on a timer.
@ -200,8 +200,8 @@ people change and re-structure this in various ways to better fit their ideas.
- [batch_cmds.ev](github:evennia/game_template/world/batch_cmds.ev) - This is an `.ev` file, which is essentially
just a list of Evennia commands to execute in sequence. This one is empty and ready to expand on. The
[Tutorial World](./Tutorial-World-Introduction) was built with such a batch-file.
- [prototypes.py](github:evennia/game_template/world/prototypes.py) - A [prototype](../../../Components/Prototypes) is a way
[Tutorial World](./Tutorial-World-Introduction.md) was built with such a batch-file.
- [prototypes.py](github:evennia/game_template/world/prototypes.py) - A [prototype](../../../Components/Prototypes.md) is a way
to easily vary objects without changing their base typeclass. For example, one could use prototypes to
tell that Two goblins, while both of the class 'Goblin' (so they follow the same code logic), should have different
equipment, stats and looks.

View file

@ -2,7 +2,7 @@
Now that we have learned a little about how to find things in the Evennia library, let's use it.
In the [Python classes and objects](./Python-classes-and-objects) lesson we created the dragons Fluffy, Cuddly
In the [Python classes and objects](./Python-classes-and-objects.md) lesson we created the dragons Fluffy, Cuddly
and Smaug and made them fly and breathe fire. So far our dragons are short-lived - whenever we `restart`
the server or `quit()` out of python mode they are gone.
@ -65,7 +65,7 @@ If we knew what kind of methods and resources were available on `DefaultObject`
change the way it works!
> Hint: We will get back to this, but to learn what resources an Evennia parent like `DefaultObject` offers,
> easiest is to peek at its [API documentation](api:evennia.objects.objects#DefaultObject). The docstring for
> easiest is to peek at its [API documentation](evennia.objects.objects.DefaultObject). The docstring for
> the `Object` class can also help.
One thing that Evennia classes offers and which you don't get with vanilla Python classes is _persistence_. As
@ -110,7 +110,7 @@ from `DefaultObject`, just from further away!
First reload the server as usual. We will need to create the dragon a little differently this time:
```sidebar:: Keyword arguments
```{sidebar} Keyword arguments
Keyword arguments (like `db_key="Smaug"`) is a way to
name the input arguments to a function or method. They make
@ -251,7 +251,7 @@ You are specifying exactly which typeclass you want to use to build the Giantess
desc = You see nothing special.
-------------------------------------------------------------------------------
We used the `examine` command briefly in the [lesson about building in-game](./Building-Quickstart). Now these lines
We used the `examine` command briefly in the [lesson about building in-game](./Building-Quickstart.md). 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.
@ -294,7 +294,7 @@ But the reason Evennia knows to fall back to this class is not hard-coded - it's
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
```{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
@ -357,7 +357,7 @@ You got a lot longer output this time. You have a lot more going on than a simpl
- **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
get to them in the [next lesson](./Adding-Commands.md). 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.
@ -391,7 +391,7 @@ class Character(DefaultCharacter):
> py self.get_stats()
(10, 12, 15)
```sidebar:: Tuples and lists
```{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.
@ -451,7 +451,7 @@ class Character(DefaultCharacter):
return self.db.str, self.db.dex, self.db.int
```
```sidebar:: Spaces in Attribute name?
```{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,
@ -506,7 +506,7 @@ in more detail. For now, let's give every new character some random stats to sta
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
```{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
@ -583,7 +583,7 @@ this is done (still in python multi-line mode):
> for char in Character.objects.all()
> char.at_object_creation()
```sidebar:: Database queries
```{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

View file

@ -75,7 +75,7 @@ The `parse` method is called before `func` and has access to all the same on-com
your parsing - if you wanted some other Command to also understand input on the form `<arg> with <arg>` you'd inherit
from this class and just implement the `func` needed for that command without implementing `parse` anew.
```sidebar:: Tuples and Lists
```{sidebar} Tuples and Lists
- A `list` is written as `[a, b, c, d, ...]`. You can add and grow/shrink a list after it was first created.
- A `tuple` is written as `(a, b, c, d, ...)`. A tuple cannot be modified once it is created.
@ -143,8 +143,8 @@ change (no code changed, only stuff in the database).
## Adding a Command to an object
The commands of a cmdset attached to an object with `obj.cmdset.add()` will by default be made available to that object
but _also to those in the same location as that object_. If you did the [Building introduction](./Building-Quickstart)
you've seen an example of this with the "Red Button" object. The [Tutorial world](./Tutorial-World-Introduction)
but _also to those in the same location as that object_. If you did the [Building introduction](./Building-Quickstart.md)
you've seen an example of this with the "Red Button" object. The [Tutorial world](./Tutorial-World-Introduction.md)
also has many examples of objects with commands on them.
To show how this could work, let's put our 'hit' Command on our simple `sword` object from the previous section.
@ -161,7 +161,7 @@ Let's try to swing it!
hit-1 (sword #11)
hit-2
```sidebar:: Multi-matches
```{sidebar} Multi-matches
Some game engines will just pick the first hit when finding more than one.
Evennia will always give you a choice. The reason for this is that Evennia
@ -206,7 +206,7 @@ Let's get a little ahead of ourselves and make it so you have to _hold_ the swor
be available. This involves a _Lock_. We've cover locks in more detail later, just know that they are useful
for limiting the kind of things you can do with an object, including limiting just when you can call commands on
it.
```sidebar:: Locks
```{sidebar} Locks
Evennia Locks are defined as a mini-language defined in `lockstrings`. The lockstring
is on a form `<situation>:<lockfuncs>`, where `situation` determines when this
@ -221,7 +221,7 @@ this object if you are _holding_ the object (that is, it's in your inventory).
For locks to work, you cannot be _superuser_, since the superuser passes all locks. You need to `quell` yourself
first:
```sidebar:: quell/unquell
```{sidebar} quell/unquell
Quelling allows you as a developer to take on the role of players with less
priveleges. This is useful for testing and debugging, in particular since a
@ -320,7 +320,7 @@ class SessionCmdSet(default_cmds.SessionCmdSet):
#
```
```sidebar:: super()
```{sidebar} super()
The `super()` function refers to the parent of the current class and is commonly
used to call same-named methods on the parent.
@ -474,7 +474,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
self.add(mycommands.MyCmdGet)
# ...
```
```sidebar:: Another way
```{sidebar} Another way
Instead of adding `MyCmdGet` explicitly in default_cmdset.py,
you could also add it to `mycommands.MyCmdSet` and let it be

View file

@ -5,7 +5,7 @@ which is a mature and professional programming language that is very fast to wor
That said, even though Python is widely considered easy to learn, we can only cover the most immediately
important aspects of Python in this series of starting tutorials. Hopefully we can get you started
but then you'll need to continue learning from there. See our [link section](../../../Links) for finding
but then you'll need to continue learning from there. See our [link section](../../../Links.md) for finding
more reference material and dedicated Python tutorials.
> While this will be quite basic if you are an experienced developer, you may want to at least
@ -17,7 +17,7 @@ superuser powers back:
unquell
### Evennia Hello world
## Evennia Hello world
The `py` Command (or `!`, which is an alias) allows you as a superuser to execute raw Python from in-
game. This is useful for quick testing. From the game's input line, enter the following:
@ -25,7 +25,7 @@ game. This is useful for quick testing. From the game's input line, enter the fo
> py print("Hello World!")
```sidebar:: Command input
```{sidebar} Command input
The line with `>` indicates input to enter in-game, while the lines below are the
expected return from that input.
@ -43,7 +43,7 @@ Python accepts both. A third variant is triple-quotes (`"""..."""` or `'''...'''
lines and are common for larger text-blocks. The way we use the `py` command right now only supports
single-line input however.
### Making some text 'graphics'
## Making some text 'graphics'
When making a text-game you will, unsurprisingly, be working a lot with text. Even if you have the occational
button or even graphical element, the normal process is for the user to input commands as
@ -72,7 +72,8 @@ is to use the `.format` _method_ of the string:
> py print("This is a {} idea!".format("good"))
This is a good idea!
```sidebar:: Functions and Methods
```{eval-rst}
.. sidebar:: Functions and Methods
Function:
Something that performs and action when you `call` it with zero or more `arguments`. A function
@ -111,7 +112,7 @@ To separate two Python instructions on the same line, you use the semi-colon, `;
> py a = "awesome sauce" ; print("This is {}!".format(a))
This is awesome sauce!
```warning:: MUD clients and semi-colon
```{warning} MUD clients and semi-colon
Some MUD clients use the semi-colon `;` to split client-inputs
into separate sends. If so, the above will give an error. Most clients allow you to
@ -169,7 +170,7 @@ gives the normal text color. You can also use RGB (Red-Green-Blue) values from 0
Use the commands `color ansi` or `color xterm` to see which colors are available. Experiment!
### Importing code from other modules
## Importing code from other modules
As we saw in the previous sections, we used `.format` to format strings and `me.msg` to access
the `msg` method on `me`. This use of the full-stop character is used to access all sorts of resources,
@ -191,7 +192,7 @@ For now, only add one line to `test.py`:
print("Hello World!")
```
```sidebar:: Python module
```{sidebar} Python module
This is a text file with the `.py` file ending. A module
contains Python source code and from within Python one can
@ -306,7 +307,7 @@ print our text. We can now redo this as many times as we want without having to
> py import world.test ; world.test.hello_world()
Hello world!
### Sending text to others
## Sending text to others
The `print` command is a standard Python structure. We can use that here in the `py` command since
we can se the output. It's great for debugging and quick testing. But if you need to send a text
@ -332,7 +333,7 @@ For now, `print` and `me.msg` behaves the same, just remember that `print` is ma
debugging and `.msg()` will be more useful for you in the future.
### Parsing Python errors
## Parsing Python errors
Let's try this new text-sending in the function we just created. Go back to
your `test.py` file and Replace the function with this instead:
@ -355,7 +356,7 @@ File "./world/test.py", line 2, in hello_world
NameError: name 'me' is not defined
```
```sidebar:: Errors in the logs
```{sidebar} Errors in the logs
In regular use, tracebacks will often appear in the log rather than
in the game. Use `evennia --log` to view the log in the terminal. Make
@ -386,7 +387,7 @@ reserved word (as mentioned, it's just something Evennia came up with for conven
command). As far as the module is concerned `me` is an unfamiliar name, appearing out of nowhere.
Hence the `NameError`.
### Passing arguments to functions
## Passing arguments to functions
We know that `me` exists at the point when we run the `py` command, because we can do `py me.msg("Hello World!")`
with no problem. So let's _pass_ that me along to the function so it knows what it should be.
@ -419,7 +420,7 @@ suitable targets.
>and the concept of _Leap before you Look_.
### Finding others to send to
## Finding others to send to
Let's wrap up this first Python `py` crash-course by finding someone else to send to.
@ -431,7 +432,7 @@ On the game command-line, let's create a mirror:
> create/drop mirror:contrib.tutorial_examples.mirror.TutorialMirror
```sidebar:: Creating objects
```{sidebar} Creating objects
The `create` command was first used to create boxes in the
`Building Stuff <Building-Quickstart>`_ tutorial. Note how it
@ -466,7 +467,7 @@ Make sure you are in the same location as the mirror and try:
`me.search("name")` will, by default, search and _return_ an object with the given name found in _the same location_
as the `me` object is. If it can't find anything you'll see an error.
```sidebar:: Function returns
```{sidebar} Function returns
Whereas a function like `print` only prints its arguments, it's very common
for functions/methods to `return` a result of some kind. Think of the function
@ -490,7 +491,7 @@ The mirror is useful for testing because its `.msg` method just echoes whatever
would be to talk to a player character, in which case the text you sent would have appeared in their game client.
### Multi-line py
## Multi-line py
So far we have use `py` in single-line mode, using `;` to separate multiple inputs. This is very convenient
when you want to do some quick testing. But you can also start a full multi-line Python interactive interpreter
@ -524,7 +525,7 @@ the `>>>`). For brevity in this tutorual we'll turn the echo off. First exit `py
[GCC 8.2.0] on Linux
[py mode - quit() to exit]
```sidebar:: interactive py
```{sidebar} interactive py
- Start with `py`.
- Use `py/noecho` if you don't want your input to be echoed for every line.
@ -602,7 +603,7 @@ get at the first of them (counting starts from 0).
Use `Ctrl-D` (`Cmd-D` on Mac) or `quit()` to exit the Python console.
### ipython
## ipython
The default Python shell is quite limited and ugly. It's *highly* recommended to install `ipython` instead. This
is a much nicer, third-party Python interpreter with colors and many usability improvements.
@ -614,8 +615,7 @@ If `ipython` is installed, `evennia shell` will use it automatically.
evennia shell
...
IPython 7.4.0 -- An enhanced Interactive Python. Type '?' for help
In [1]:
You now have Tab-completion:
In [1]: You now have Tab-completion:
> import evennia
> evennia.<TAB>
@ -631,7 +631,7 @@ want to see the entire source code.
As for the normal python interpreter, use `Ctrl-D`/`Cmd-D` or `quit()` to exit ipython.
```important:: Persistent code
```{important} Persistent code
Common for both `py` and `python`/`ipython` is that the code you write is not persistent - it will
be gone after you shut down the interpreter (but ipython will remember your input history). For making long-lasting
@ -639,7 +639,7 @@ As for the normal python interpreter, use `Ctrl-D`/`Cmd-D` or `quit()` to exit i
```
## Conclusions
# Conclusions
This covers quite a lot of basic Python usage. We printed and formatted strings, defined our own
first function, fixed an error and even searched and talked to a mirror! Being able to access

View file

@ -5,13 +5,13 @@ We have also taken a look at what our game dir looks and what is where. Now we'l
## Importing things
No one writes something as big as an online game in one single huge file. Instead one breaks up the
code into separate files (modules). Each module is dedicated to different purposes. Not only does
it make things cleaner, organized and easier to understand. It also makes it easier to re-use code -
you just import the resources you need and know you only get just what you requested. This makes
No one writes something as big as an online game in one single huge file. Instead one breaks up the
code into separate files (modules). Each module is dedicated to different purposes. Not only does
it make things cleaner, organized and easier to understand. It also makes it easier to re-use code -
you just import the resources you need and know you only get just what you requested. This makes
it much easier to find errors and to know what code is good and which has issues.
> Evennia itself uses your code in the same way - you just tell it where a particular type of code is,
> Evennia itself uses your code in the same way - you just tell it where a particular type of code is,
and it will import and use it (often instead of its defaults).
We have already successfully imported things, for example:
@ -19,11 +19,11 @@ We have already successfully imported things, for example:
> py import world.test ; world.test.hello_world(me)
Hello World!
In this example, on your hard drive, the files looks like this:
In this example, on your hard drive, the files looks like this:
```
mygame/
world/
world/
test.py <- inside this file is a function hello_world
```
@ -35,71 +35,73 @@ def hello_world(who):
who.msg("Hello World!")
```
```sidebar:: Remember:
```{eval-rst}
- Indentation matters in Python
.. sidebar:: Remember:
- Indentation matters in Python
- So does capitalization
- Use 4 `spaces` to indent, not tabs
- Empty lines are fine
- Anything on a line after a `#` is a `comment`, ignored by Python
```
The _python_path_ describes the relation between Python resources, both between and inside
Python _modules_ (that is, files ending with .py). A python-path separates each part of the
path `.` and always skips the `.py` file endings. Also, Evennia already knows to start looking
The _python_path_ describes the relation between Python resources, both between and inside
Python _modules_ (that is, files ending with .py). A python-path separates each part of the
path `.` and always skips the `.py` file endings. Also, Evennia already knows to start looking
for python resources inside `mygame/` so this should never be specified. Hence
import world.test
import world.test
The `import` Python instruction loads `world.test` so you have it available. You can now go "into"
this module to get to the function you want:
world.test.hello_world(me)
Using `import` like this means that you have to specify the full `world.test` every time you want
to get to your function. Here's a more powerful form of import:
to get to your function. Here's a more powerful form of import:
from world.test import hello_world
The `from ... import ...` is very, very common as long as you want to get something with a longer
The `from ... import ...` is very, very common as long as you want to get something with a longer
python path. It imports `hello_world` directly, so you can use it right away!
> py from world.test import hello_world ; hello_world(me)
Hello World!
Let's say your `test.py` module had a bunch of interesting functions. You could then import them
all one by one:
all one by one:
from world.test import hello_world, my_func, awesome_func
If there were _a lot_ of functions, you could instead just import `test` and get the function
If there were _a lot_ of functions, you could instead just import `test` and get the function
from there when you need (without having to give the full `world.test` every time):
> from world import test ; test.hello_world(me
Hello World!
You can also _rename_ stuff you import. Say for example that the module you import to already
Hello World!
You can also _rename_ stuff you import. Say for example that the module you import to already
has a function `hello_world` but we also want to use the one from `world/test.py`:
from world.test import hello_world as test_hello_world
The form `from ... import ... as ...` renames the import.
The form `from ... import ... as ...` renames the import.
> from world.test import hello_world as hw ; hw(me)
Hello World!
> Avoid renaming unless it's to avoid a name-collistion like above - you want to make things as
> easy to read as possible, and renaming adds another layer of potential confusion.
In [the basic intro to Python](./Python-basic-introduction) we learned how to open the in-game
multi-line interpreter.
> py
> Avoid renaming unless it's to avoid a name-collistion like above - you want to make things as
> easy to read as possible, and renaming adds another layer of potential confusion.
In [the basic intro to Python](./Python-basic-introduction.md) we learned how to open the in-game
multi-line interpreter.
> py
Evennia Interactive Python mode
Python 3.7.1 (default, Oct 22 2018, 11:21:55)
[GCC 8.2.0] on Linux
[py mode - quit() to exit]
[py mode - quit() to exit]
You now only need to import once to use the imported function over and over.
> from world.test import hello_world
@ -112,14 +114,14 @@ You now only need to import once to use the imported function over and over.
> quit()
Closing the Python console.
The same goes when writing code in a module - in most Python modules you will see a bunch of
The same goes when writing code in a module - in most Python modules you will see a bunch of
imports at the top, resources that are then used by all code in that module.
## On classes and objects
## On classes and objects
Now that we know about imports, let look at a real Evennia module and try to understand it.
Open `mygame/typeclasses/objects.py` in your text editor of choice.
Open `mygame/typeclasses/objects.py` in your text editor of choice.
```python
"""
@ -134,30 +136,30 @@ class Object(DefaultObject):
pass
```
```sidebar:: Docstrings vs Comments
```{sidebar} Docstrings vs Comments
A docstring is not the same as a comment (created by `#`). A
docstring is not ignored by Python but is an integral part of the thing
A docstring is not the same as a comment (created by `#`). A
docstring is not ignored by Python but is an integral part of the thing
it is documenting (the module and the class in this case).
```
The real file is much longer but we can ignore the multi-line strings (`""" ... """`). These serve
as documentation-strings, or _docstrings_ for the module (at the top) and the `class` below.
The real file is much longer but we can ignore the multi-line strings (`""" ... """`). These serve
as documentation-strings, or _docstrings_ for the module (at the top) and the `class` below.
Below the module doc string we have the import. In this case we are importing a resource
from the core `evennia` library itself. We will dive into this later, for now we just treat this
as a black box.
as a black box.
Next we have a `class` named `Object`, which _inherits_ from `DefaultObject`. This class doesn't
actually do anything on its own, its only code (except the docstring) is `pass` which means,
well, to pass and don't do anything.
well, to pass and don't do anything.
We will get back to this module in the [next lesson](./Learning-Typeclasses). First we need to do a
little detour to understand what a 'class', an 'object' or 'instance' is. These are fundamental
things to understand before you can use Evennia efficiently.
```sidebar:: OOP
We will get back to this module in the [next lesson](./Learning-Typeclasses.md). First we need to do a
little detour to understand what a 'class', an 'object' or 'instance' is. These are fundamental
things to understand before you can use Evennia efficiently.
```{sidebar} OOP
Classes, objects, instances and inheritance are fundamental to Python. This and some
other concepts are often clumped together under the term Object-Oriented-Programming (OOP).
Classes, objects, instances and inheritance are fundamental to Python. This and some
other concepts are often clumped together under the term Object-Oriented-Programming (OOP).
```
### Classes and instances
@ -179,57 +181,58 @@ class Monster:
```
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
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
back to the `key` on the class.
```sidebar:: Terms
```{eval-rst}
.. sidebar:: Terms
- A `class` is a code template describing a 'type' of something
- An `object` is an `instance` of a `class`. Like using a mold to cast tin soldiers, one class can be `instantiated` into any number of object-instances.
- An `object` is an `instance` of a `class`. Like using a mold to cast tin soldiers, one class can be `instantiated` into any number of object-instances.
```
A class is just a template. Before it can be used, we must create an _instance_ of the class. If
`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:
`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 = Monster()
Let's try it in-game (we use multi-line mode, it's easier)
> py
> py
> from typeclasses.monsters import Monster
> fluffy = Monster()
> fluffy.move_around()
Monster is moving!
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.
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
> there when defining the method, we _never_ add it explicitly when we call the method (Python
> will add the correct `self` for us automatically behind the scenes).
> Note how we _didn't_ call the method as `fluffy.move_around(self)`. While the `self` has to be
> there when defining the method, we _never_ add it explicitly when we call the method (Python
> will add the correct `self` for us automatically behind the scenes).
Let's create the sibling of Fluffy, Cuddly:
> cuddly = Monster()
> cuddly.move_around()
Monster is moving!
Monster is moving!
We now have two dragons and they'll hang around until with call `quit()` to exit this Python
instance. We can have them move as many times as we want. But no matter how many dragons we
We now have two dragons and they'll hang around until with call `quit()` to exit this Python
instance. We can have them move as many times as we want. But no matter how many dragons we
create, they will all show the same printout since `key` is always fixed as "Monster".
Let's make the class a little more flexible:
Let's make the class a little more flexible:
```python
class Monster:
def __init__(self, key):
self.key = key
self.key = key
def move_around(self):
print(f"{self.key} is moving!")
@ -237,7 +240,7 @@ class Monster:
```
The `__init__` is a special method that Python recognizes. If given, this handles extra arguments
when you instantiate a new Monster. 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:
@ -245,13 +248,13 @@ way:
> quit()
Python Console is closing.
> reload
Or you can use a separate terminal and restart from outside the game:
```sidebar:: On reloading
Or you can use a separate terminal and restart from outside the game:
```{sidebar} On reloading
Reloading with the python mode gets a little annoying since you need to redo everything
after every reload. Just keep in mind that during regular development you will not be
working this way. The in-game python mode is practical for quick fixes and experiments like
after every reload. Just keep in mind that during regular development you will not be
working this way. The in-game python mode is practical for quick fixes and experiments like
this, but actual code is normally written externally, in python modules.
```
@ -259,50 +262,50 @@ Or you can use a separate terminal and restart from outside the game:
Either way you'll need to go into `py` again:
> py
> py
> from typeclasses.monsters import Monster
fluffy = Monster("Fluffy")
fluffy.move_around()
Fluffy is moving!
Fluffy is moving!
Now we passed `"Fluffy"` as an argument to the class. This went into `__init__` and set `self.key`, which we
Now we passed `"Fluffy"` as an argument to the class. This went into `__init__` and set `self.key`, which we
later used to print with the right name! Again, note that we didn't include `self` when calling.
### What's so good about objects?
### What's so good about objects?
So far all we've seen a class do is to behave our first `hello_world` function but more complex. We
So far all we've seen a class do is to behave our first `hello_world` function but more complex. We
could just have made a function:
```python
def monster_move_around(key):
print(f"{key} is moving!")
print(f"{key} is moving!")
```
The difference between the function and an instance of a class (the object), is that the
The difference between the function and an instance of a class (the object), is that the
object retains _state_. Once you called the function it forgets everything about what you called
it with last time. The object, on the other hand, remembers changes:
it with last time. The object, on the other hand, remembers changes:
> fluffy.key = "Cuddly"
> fluffy.move_around()
Cuddly is moving!
The `fluffy` object's `key` was changed to "Cuddly" for as long as it's around. This makes objects
extremely useful for representing and remembering collections of data - some of which can be other
objects in turn:
Cuddly is moving!
- A player character with all its stats
The `fluffy` object's `key` was changed to "Cuddly" for as long as it's around. This makes objects
extremely useful for representing and remembering collections of data - some of which can be other
objects in turn:
- A player character with all its stats
- A monster with HP
- A chest with a number of gold coins in it
- A room with other objects inside it
- The current policy positions of a political party
- A rule with methods for resolving challenges or roll dice
- A multi-dimenstional data-point for a complex economic simulation
- And so much more!
- And so much more!
### Classes can have children
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.
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/monsters.py` with another class:
@ -312,9 +315,9 @@ class Monster:
"""
This is a base class for Monster.
"""
def __init__(self, key):
self.key = key
self.key = key
def move_around(self):
print(f"{self.key} is moving!")
@ -329,7 +332,7 @@ class Dragon(Monster):
print(f"{self.key} flies through the air high above!")
def firebreath(self):
"""
"""
Let our dragon breathe fire.
"""
print(f"{self.key} breathes fire!")
@ -339,10 +342,10 @@ class Dragon(Monster):
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 `Monster` is the _parent_ of `Dragon` but adding
the parent in parenthesis. `class Classname(Parent)` is the way to do this.
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
```{sidebar} Multi-inheritance
It's possible to add more comma-separated parents to a class. You should usually avoid
this until you `really` know what you are doing. A single parent will be enough for almost
@ -352,61 +355,61 @@ 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.monsters import Dragon
> py
> from typeclasses.monsters import Dragon
> smaug = Dragon("Smaug")
> smaug.move_around()
Smaug flies through the air high above!
> smaug.firebreath()
Smaug breathes fire!
Because we didn't implement `__init__` in `Dragon`, we got the one from `Monster` instead. But since we
implemented our own `move_around` in `Dragon`, it _overrides_ the one in `Monster`. And `firebreath` is only
Smaug breathes fire!
Because we didn't implement `__init__` in `Dragon`, we got the one from `Monster` instead. But since we
implemented our own `move_around` in `Dragon`, it _overrides_ the one in `Monster`. And `firebreath` is only
available for `Dragon`s of course. Having that on `Monster` would not have made much sense, since not every monster
can breathe fire.
can breathe fire.
One can also force a class to use resources from the parent even if you are overriding some of it. This is done
with the `super()` method. Modify your `Dragon` class as follows:
```python
# ...
# ...
class Dragon(Monster):
def move_around(self):
super().move_around()
print("The world trembles.")
# ...
```
> Keep `Monster` and the `firebreath` method, `# ...` indicates the rest of the code is untouched.
> Keep `Monster` and the `firebreath` method, `# ...` indicates the rest of the code is untouched.
>
The `super().move_around()` line means that we are calling `move_around()` on the parent of the class. So in this
case, we will call `Monster.move_around` first, before doing our own thing.
The `super().move_around()` line means that we are calling `move_around()` on the parent of the class. So in this
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
> from typeclasses.monsters import Dragon
> py
> from typeclasses.monsters import Dragon
> smaug = Dragon("Smaug")
> smaug.move_around()
Smaug is moving!
The world trembles.
We can see that `Monster.move_around()` is calls first and prints "Smaug is moving!", followed by the extra bit
We can see that `Monster.move_around()` is calls first and prints "Smaug is moving!", followed by the extra bit
about the trembling world we added in the `Dragon` class.
Inheritance is very powerful because it allows you to organize and re-use code while only adding the special things
you want to change. Evennia uses this concept a lot.
you want to change. Evennia uses this concept a lot.
## Summary
We have created our first dragons from classes. We have learned a little about how you _instantiate_ a class
We have created our first dragons from classes. We have learned a little about how you _instantiate_ a class
into an _object_. We have seen some examples of _inheritance_ and we tested to _override_ a method in the parent
with one in the child class. We also used `super()` to good effect.
We have used pretty much raw Python so far. In the coming lessons we'll start to look at the extra bits that Evennia
We have used pretty much raw Python so far. In the coming lessons we'll start to look at the extra bits that Evennia
provides. But first we need to learn just where to find everything.

View file

@ -1,30 +1,30 @@
# Searching for things
We have gone through how to create the various entities in Evennia. But creating something is of little use
if we cannot find and use it afterwards.
We have gone through how to create the various entities in Evennia. But creating something is of little use
if we cannot find and use it afterwards.
## Main search functions
The base tools are the `evennia.search_*` functions, such as `evennia.search_object`.
The base tools are the `evennia.search_*` functions, such as `evennia.search_object`.
rose = evennia.search_object(key="rose")
acct = evennia.search_account(key="MyAccountName", email="foo@bar.com")
```sidebar:: Querysets
```{sidebar} Querysets
What is returned from the main search functions is actually a `queryset`. They can be
What is returned from the main search functions is actually a `queryset`. They can be
treated like lists except that they can't modified in-place. We'll discuss querysets in
the `next lesson` <Django-queries>`_.
```
Strings are always case-insensitive, so searching for `"rose"`, `"Rose"` or `"rOsE"` give the same results.
It's important to remember that what is returned from these search methods is a _listing_ of 0, one or more
elements - all the matches to your search. To get the first match:
It's important to remember that what is returned from these search methods is a _listing_ of 0, one or more
elements - all the matches to your search. To get the first match:
rose = rose[0]
rose = rose[0]
Often you really want all matches to the search parameters you specify. In other situations, having zero or
more than one match is a sign of a problem and you need to handle this case yourself.
Often you really want all matches to the search parameters you specify. In other situations, having zero or
more than one match is a sign of a problem and you need to handle this case yourself.
the_one_ring = evennia.search_object(key="The one Ring")
if not the_one_ring:
@ -35,16 +35,16 @@ more than one match is a sign of a problem and you need to handle this case your
# ok - exactly one ring found
the_one_ring = the_one_ring[0]
There are equivalent search functions for all the main resources. You can find a listing of them
[in the Search functions section](../../../Evennia-API) of the API frontpage.
There are equivalent search functions for all the main resources. You can find a listing of them
[in the Search functions section](../../../Evennia-API.md) of the API frontpage.
## Searching using Object.search
On the `DefaultObject` is a `.search` method which we have already tried out when we made Commands. For
this to be used you must already have an object available:
On the `DefaultObject` is a `.search` method which we have already tried out when we made Commands. For
this to be used you must already have an object available:
rose = obj.search("rose")
The `.search` method wraps `evennia.search_object` and handles its output in various ways.
- By default it will always search for objects among those in `obj.location.contents` and `obj.contents` (that is,
@ -52,7 +52,7 @@ things in obj's inventory or in the same room).
- It will always return exactly one match. If it found zero or more than one match, the return is `None`.
- On a no-match or multimatch, `.search` will automatically send an error message to `obj`.
So this method handles error messaging for you. A very common way to use it is in commands:
So this method handles error messaging for you. A very common way to use it is in commands:
```python
from evennia import Command
@ -68,21 +68,21 @@ class MyCommand(Command):
return
```
Remember, `self.caller` is the one calling the command. This is usually a Character, which
inherits from `DefaultObject`! This (rather stupid) Command searches for an object named "foo" in
the same location. If it can't find it, `foo` will be `None`. The error has already been reported
to `self.caller` so we just abort with `return`.
Remember, `self.caller` is the one calling the command. This is usually a Character, which
inherits from `DefaultObject`! This (rather stupid) Command searches for an object named "foo" in
the same location. If it can't find it, `foo` will be `None`. The error has already been reported
to `self.caller` so we just abort with `return`.
You can use `.search` to find anything, not just stuff in the same room:
You can use `.search` to find anything, not just stuff in the same room:
volcano = self.caller.search("Volcano", global=True)
If you only want to search for a specific list of things, you can do so too:
If you only want to search for a specific list of things, you can do so too:
stone = self.caller.search("MyStone", candidates=[obj1, obj2, obj3, obj4])
This will only return a match if MyStone is one of the four provided candidate objects. This is quite powerful,
here's how you'd find something only in your inventory:
here's how you'd find something only in your inventory:
potion = self.caller.search("Healing potion", candidates=self.caller.contents)
@ -90,25 +90,25 @@ You can also turn off the automatic error handling:
swords = self.caller.search("Sword", quiet=True)
With `quiet=True` the user will not be notified on zero or multi-match errors. Instead you are expected to handle this
yourself and what you get back is now a list of zero, one or more matches!
With `quiet=True` the user will not be notified on zero or multi-match errors. Instead you are expected to handle this
yourself and what you get back is now a list of zero, one or more matches!
## What can be searched for
These are the main database entities one can search for:
- [Objects](../../../Components/Objects)
- [Accounts](../../../Components/Accounts)
- [Scripts](../../../Components/Scripts),
- [Channels](../../../Components/Communications#channels),
- [Messages](Communication#Msg)
- [Help Entries](../../../Components/Help-System).
- [Objects](../../../Components/Objects.md)
- [Accounts](../../../Components/Accounts.md)
- [Scripts](../../../Components/Scripts.md),
- [Channels](../../../Components/Channels.md),
- [Messages](../../../Components/Msg.md)
- [Help Entries](../../../Components/Help-System.md).
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?
### Search by key
### Search by key
The `key` is the name of the entity. Searching for this is always case-insensitive.
@ -118,29 +118,29 @@ Objects and Accounts can have any number of aliases. When searching for `key` th
you can't easily search only for aliases.
rose.aliases.add("flower")
If the above `rose` has a `key` `"Rose"`, it can now also be found by searching for `flower`. In-game
If the above `rose` has a `key` `"Rose"`, it can now also be found by searching for `flower`. In-game
you can assign new aliases to things with the `alias` command.
### Search by location
Only Objects (things inheriting from `evennia.DefaultObject`) has a location. This is usually a room.
Only Objects (things inheriting from `evennia.DefaultObject`) has a location. This is usually a room.
The `Object.search` method will automatically limit it search by location, but it also works for the
general search function. If we assume `room` is a particular Room instance,
general search function. If we assume `room` is a particular Room instance,
chest = evennia.search_object("Treasure chest", location=room)
chest = evennia.search_object("Treasure chest", location=room)
### Search by Tags
### Search by Tags
Think of a [Tag](../../../Components/Tags) as the label the airport puts on your luggage when flying.
Everyone going on the same plane gets a tag grouping them together so the airport can know what should
Think of a [Tag](../../../Components/Tags.md) as the label the airport puts on your luggage when flying.
Everyone going on the same plane gets a tag grouping them together so the airport can know what should
go to which plane. Entities in Evennia can be grouped in the same way. Any number of tags can be attached
to each object.
to each object.
rose.tags.add("flowers")
daffodil.tags.add("flowers")
tulip.tags.add("flowers")
You can now find all flowers using the `search_tag` function:
all_flowers = evennia.search_tag("flowers")
@ -150,100 +150,100 @@ Tags can also have categories. By default this category is `None` which is also
silmarillion.tags.add("fantasy", category="books")
ice_and_fire.tags.add("fantasy", category="books")
mona_lisa_overdrive.tags.add("cyberpunk", category="books")
Note that if you specify the tag you _must_ also include its category, otherwise that category
Note that if you specify the tag you _must_ also include its category, otherwise that category
will be `None` and find no matches.
all_fantasy_books = evennia.search_tag("fantasy") # no matches!
all_fantasy_books = evennia.search_tag("fantasy", category="books")
all_fantasy_books = evennia.search_tag("fantasy") # no matches!
all_fantasy_books = evennia.search_tag("fantasy", category="books")
Only the second line above returns the two fantasy books. If we specify a category however,
we can get all tagged entities within that category:
we can get all tagged entities within that category:
all_books = evennia.search_tag(category="books")
This gets all three books.
This gets all three books.
### Search by Attribute
We can also search by the [Attributes](../../../Components/Attributes) associated with entities.
We can also search by the [Attributes](../../../Components/Attributes.md) associated with entities.
For example, let's give our rose thorns:
For example, let's give our rose thorns:
rose.db.has_thorns = True
wines.db.has_thorns = True
daffodil.db.has_thorns = False
Now we can find things attribute and the value we want it to have:
daffodil.db.has_thorns = False
Now we can find things attribute and the value we want it to have:
is_ouch = evennia.search_object_attribute("has_thorns", True)
This returns the rose and the wines.
This returns the rose and the wines.
> Searching by Attribute can be very practical. But if you plan to do a search very often, searching
> by-tag is generally faster.
> by-tag is generally faster.
### Search by Typeclass
Sometimes it's useful to find all objects of a specific Typeclass. All of Evennia's search tools support this.
all_roses = evennia.search_object(typeclass="typeclasses.flowers.Rose")
all_roses = evennia.search_object(typeclass="typeclasses.flowers.Rose")
If you have the `Rose` class already imported you can also pass it directly:
all_roses = evennia.search_object(typeclass=Rose)
You can also search using the typeclass itself:
all_roses = Rose.objects.all()
This last way of searching is a simple form of a Django _query_. This is a way to express SQL queries using
Python. We'll cover this some more as an [Extra-credits](#Extra-Credits) section at the end of this lesson.
### Search by dbref
The database id or `#dbref` is unique and never-reused within each database table. In search methods you can
replace the search for `key` with the dbref to search for. This must be written as a string `#dbref`:
This last way of searching is a simple form of a Django _query_. This is a way to express SQL queries using
Python.
### Search by dbref
The database id or `#dbref` is unique and never-reused within each database table. In search methods you can
replace the search for `key` with the dbref to search for. This must be written as a string `#dbref`:
the_answer = self.caller.search("#42")
eightball = evennia.search_object("#8")
eightball = evennia.search_object("#8")
Since `#dbref` is always unique, this search is always global.
Since `#dbref` is always unique, this search is always global.
```warning:: Relying on #dbrefs
```{warning} Relying on #dbrefs
You may be used to using #dbrefs a lot from other codebases. It is however considered
You may be used to using #dbrefs a lot from other codebases. It is however considered
`bad practice` in Evennia to rely on hard-coded #dbrefs. It makes your code hard to maintain
and tied to the exact layout of the database. In 99% of cases you should pass the actual objects
around and search by key/tags/attribute instead.
and tied to the exact layout of the database. In 99% of cases you should pass the actual objects
around and search by key/tags/attribute instead.
```
## Finding objects relative each other
Let's consider a `chest` with a `coin` inside it. The chests stand in a room `dungeon`. In the dungeon is also
a `door`. This is an exit leading outside.
a `door`. This is an exit leading outside.
- `coin.location` is `chest`.
- `chest.location` is `dungeon`.
- `door.location` is `dungeon`.
- `room.location` is `None` since it's not inside something else.
- `room.location` is `None` since it's not inside something else.
One can use this to find what is inside what. For example, `coin.location.location` is the `room`.
One can use this to find what is inside what. For example, `coin.location.location` is the `room`.
We can also find what is inside each object. This is a list of things.
- `room.contents` is `[chest, door]`
- `chest.contents` is `[coin]`
- `coin.contents` is `[]`, the empty list since there's nothing 'inside' the coin.
- `door.contents` is `[]` too.
- `door.contents` is `[]` too.
A convenient helper is `.contents_get` - this allows to restrict what is returned:
A convenient helper is `.contents_get` - this allows to restrict what is returned:
- `room.contents_get(exclude=chest)` - this returns everything in the room except the chest (maybe it's hidden?)
There is a special property for finding exits:
There is a special property for finding exits:
- `room.exits` is `[door]`
- `room.exits` is `[door]`
- `coin.exits` is `[]` (same for all the other objects)
There is a property `.destination` which is only used by exits:
@ -251,11 +251,11 @@ There is a property `.destination` which is only used by exits:
- `door.destination` is `outside` (or wherever the door leads)
- `room.destination` is `None` (same for all the other non-exit objects)
## Summary
## Summary
Knowing how to find things is important and the tools from this section will serve you well. For most of your needs
these tools will be all you need ...
... but not always. In the next lesson we will dive further into more complex searching when we look at
... but not always. In the next lesson we will dive further into more complex searching when we look at
Django queries and querysets in earnest.

View file

@ -1,49 +1,51 @@
# Starting Tutorial (Part 1)
```sidebar:: Tutorial Parts
```{eval-rst}
.. sidebar:: Tutorial Parts
**Part 1: What we have**
A tour of Evennia and how to use the tools, including an introduction to Python.
Part 2: `What we want <../Part2/Starting-Part2.html>`_
Planning our tutorial game and what to think about when planning your own in the future.
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
Getting down to the meat of extending Evennia to make our game
Part 4: `Using what we created <../Part4/Starting-Part4.html>`_
Building a tech-demo and world content to go with our code
Part 5: `Showing the world <../Part5/Starting-Part5.html>`_
Taking our new game online and let players try it out
**Part 1: What we have**
A tour of Evennia and how to use the tools, including an introduction to Python.
Part 2: `What we want <../Part2/Starting-Part2.html>`_
Planning our tutorial game and what to think about when planning your own in the future.
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
Getting down to the meat of extending Evennia to make our game
Part 4: `Using what we created <../Part4/Starting-Part4.html>`_
Building a tech-demo and world content to go with our code
Part 5: `Showing the world <../Part5/Starting-Part5.html>`_
Taking our new game online and let players try it out
```
Welcome to Evennia! This multi-part Tutorial will help you get off the ground. It consists
of five parts, each with several lessons. You can pick what seems interesting, but if you
follow through to the end you will have created a little online game of your own to play
and share with others!
of five parts, each with several lessons. You can pick what seems interesting, but if you
follow through to the end you will have created a little online game of your own to play
and share with others!
## Lessons of Part 1 - "What we have"
1. Introduction (you are here)
1. [Building stuff](./Building-Quickstart)
1. [The Tutorial World](./Tutorial-World-Introduction)
1. [Python basics](./Python-basic-introduction)
1. [Game dir overview](./Gamedir-Overview)
1. [Python classes and objects](./Python-classes-and-objects)
1. [Accessing the Evennia library](./Evennia-Library-Overview)
1. [Typeclasses and Persistent objects](./Learning-Typeclasses)
1. [Making first own Commands](./Adding-Commands)
1. [Parsing and replacing default Commands](./More-on-Commands)
1. [Creating things](./Creating-Things)
1. [Searching for things](./Searching-Things)
1. [Advanced searching with Django queries](./Django-queries)
1. [Building stuff](./Building-Quickstart.md)
1. [The Tutorial World](./Tutorial-World-Introduction.md)
1. [Python basics](./Python-basic-introduction.md)
1. [Game dir overview](./Gamedir-Overview.md)
1. [Python classes and objects](./Python-classes-and-objects.md)
1. [Accessing the Evennia library](./Evennia-Library-Overview.md)
1. [Typeclasses and Persistent objects](./Learning-Typeclasses.md)
1. [Making first own Commands](./Adding-Commands.md)
1. [Parsing and replacing default Commands](./More-on-Commands.md)
1. [Creating things](./Creating-Things.md)
1. [Searching for things](./Searching-Things.md)
1. [Advanced searching with Django queries](./Django-queries.md)
In this first part we'll focus on what we get out of the box in Evennia - we'll get used to the tools,
and how to find things we are looking for. We will also dive into some of things you'll
and how to find things we are looking for. We will also dive into some of things you'll
need to know to fully utilize the system, including giving you a brief rundown of Python concepts. If you are
an experienced Python programmer, some sections may feel a bit basic, but you will at least not have seen
these concepts in the context of Evennia before.
an experienced Python programmer, some sections may feel a bit basic, but you will at least not have seen
these concepts in the context of Evennia before.
## Things you will need
## Things you will need
### A Command line
### A Command line
First of all, you need to know how to find your Terminal/Console in your OS. The Evennia server can be controlled
from in-game, but you _will_ need to use the command-line to get anywhere. Here are some starters:
@ -55,75 +57,75 @@ from in-game, but you _will_ need to use the command-line to get anywhere. Here
### A MUD client
You might already have a MUD-client you prefer. Check out the [grid of supported clients](../../../Setup/Client-Support-Grid) for aid.
If telnet's not your thing, you can also just use Evennia's web client in your browser.
You might already have a MUD-client you prefer. Check out the [grid of supported clients](../../../Setup/Client-Support-Grid.md) for aid.
If telnet's not your thing, you can also just use Evennia's web client in your browser.
> In this documentation we often use 'MUD' and 'MU' or 'MU*' interchangeably
as labels to represent all the historically different forms of text-based multiplayer game-styles,
as labels to represent all the historically different forms of text-based multiplayer game-styles,
like MUD, MUX, MUSH, MUCK, MOO and others. Evennia can be used to create all those game-styles
and more.
### An Editor
You need a text-editor to edit Python source files. Most everything that can edit and output raw
text works (so not Word).
text works (so not Word).
- [Here's a blog post summing up some of the alternatives](https://www.elegantthemes.com/blog/resources/best-code-editors) - these
things don't change much from year to year. Popular choices for Python are PyCharm, VSCode, Atom, Sublime Text and Notepad++.
- [Here's a blog post summing up some of the alternatives](https://www.elegantthemes.com/blog/resources/best-code-editors) - these
things don't change much from year to year. Popular choices for Python are PyCharm, VSCode, Atom, Sublime Text and Notepad++.
Evennia is to a very large degree coded in VIM, but that's not suitable for beginners.
> Hint: When setting up your editor, make sure that pressing TAB inserts _4 spaces_ rather than a Tab-character. Since
> Python is whitespace-aware, this will make your life a lot easier.
### Set up a game dir for the tutorial
Next you should make sure you have [installed Evennia](../../../Setup/Setup-Quickstart). If you followed the instructions
you will already have created a game-dir. You could use that for this tutorial or you may want to do the
Next you should make sure you have [installed Evennia](../../../Setup/Setup-Quickstart.md). If you followed the instructions
you will already have created a game-dir. You could use that for this tutorial or you may want to do the
tutorial in its own, isolated game dir; it's up to you.
- If you want a new gamedir for the tutorial game and already have Evennia running with another gamedir,
- If you want a new gamedir for the tutorial game and already have Evennia running with another gamedir,
first enter that gamedir and run
evennia stop
> If you want to run two parallel servers, that'd be fine too, but one would have to use
evennia stop
> If you want to run two parallel servers, that'd be fine too, but one would have to use
> different ports from the defaults, or there'd be a clash. We will go into changing settings later.
- Now go to where you want to create your tutorial-game. We will always refer to it as `mygame` so
it may be convenient if you do too:
evennia --init mygame
cd mygame
evennia migrate
cd mygame
evennia migrate
evennia start --log
Add your superuser name and password at the prompt (email is optional). Make sure you can
Add your superuser name and password at the prompt (email is optional). Make sure you can
go to `localhost:4000` in your MUD client or to [http://localhost:4001](http://localhost:4001)
in your web browser (Mac users: Try `127.0.0.1` instead of `localhost` if you have trouble).
The above `--log` flag will have Evennia output all its logs to the terminal. This will block
the terminal from other input. To leave the log-view, press `Ctrl-C` (`Cmd-C` on Mac). To see
the log again just run
the log again just run
evennia --log
You should now be good to go!
```toctree::
:hidden:
Building-Quickstart
Tutorial-World-Introduction
Python-basic-introduction
Gamedir-Overview
Python-classes-and-objects
Evennia-Library-Overview
Learning-Typeclasses
Adding-Commands
More-on-Commands
Creating-Things
Searching-Things
Django-queries
../Part2/Starting-Part2
You should now be good to go!
```
```{toctree}
:hidden:
Building-Quickstart
Tutorial-World-Introduction
Python-basic-introduction
Gamedir-Overview
Python-classes-and-objects
Evennia-Library-Overview
Learning-Typeclasses
Adding-Commands
More-on-Commands
Creating-Things
Searching-Things
Django-queries
../Part2/Starting-Part2
```

View file

@ -1,26 +1,26 @@
# The Tutorial World
# The Tutorial World
The *Tutorial World* is a small and functioning MUD-style game world shipped with Evennia.
The *Tutorial World* is a small and functioning MUD-style game world shipped with Evennia.
It's a small showcase of what is possible. It can also be useful for those who have an easier
time learning by deconstructing existing code.
time learning by deconstructing existing code.
Stand in the Limbo room and install it with
Stand in the Limbo room and install it with
batchcommand tutorial_world.build
What this does is to run the build script
[evennia/contrib/tutorial_world/build.ev](github:evennia/contrib/tutorial_world/build.ev).
This is pretty much just a list of build-commands executed in sequence by the `batchcommand` command.
This is pretty much just a list of build-commands executed in sequence by the `batchcommand` command.
Wait for the building to complete and don't run it twice. A new exit should have appeared named _Tutorial_.
The game consists of a single-player quest and has some 20 rooms that you can explore as you seek
The game consists of a single-player quest and has some 20 rooms that you can explore as you seek
to discover the whereabouts of a mythical weapon. Make sure you don't play as superuser:
quell
Enter the new exit by writing `tutorial`. Enjoy! If you succeed you will eventually
Enter the new exit by writing `tutorial`. Enjoy! If you succeed you will eventually
end up back in Limbo.
## Gameplay
![the castle off the moor](https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/22916c25-6299-453d-a221-446ec839f567/da2pmzu-46d63c6d-9cdc-41dd-87d6-1106db5a5e1a.jpg/v1/fill/w_600,h_849,q_75,strp/the_castle_off_the_moor_by_griatch_art_da2pmzu-fullview.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3siaGVpZ2h0IjoiPD04NDkiLCJwYXRoIjoiXC9mXC8yMjkxNmMyNS02Mjk5LTQ1M2QtYTIyMS00NDZlYzgzOWY1NjdcL2RhMnBtenUtNDZkNjNjNmQtOWNkYy00MWRkLTg3ZDYtMTEwNmRiNWE1ZTFhLmpwZyIsIndpZHRoIjoiPD02MDAifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6aW1hZ2Uub3BlcmF0aW9ucyJdfQ.omuS3D1RmFiZCy9OSXiIita-HxVGrBok3_7asq0rflw)
@ -39,7 +39,7 @@ face you stand where the moor meets the sea along a high, rocky coast ...*
- Look at everything. While a demo, this is not necessarily trivial, depending on your experience with
text-based adventure games. Just remember that everything can be solved or bypassed.
- Some things cannot be damaged by mortal weapons. In that case it's OK to run away. Expect
to be chased though.
to be chased though.
- Some objects are interactive in more than one way. Use the normal `help` command to get a feel for
which commands are available at any given time.
- Use the command `tutorial` to get insight behind the scenes of the game.
@ -49,14 +49,14 @@ which commands are available at any given time.
- *defend* will lower the chance to taking damage on your enemy's next attack.
- Being defeated is a part of the experience. You can't actually die, but getting knocked out
means being left in the dark ...
## Once you are done (or had enough)
Afterwards you'll either have conquered the old ruin and returned in glory and triumph ... or
Afterwards you'll either have conquered the old ruin and returned in glory and triumph ... or
you returned limping and whimpering from the challenge through `telport limbo`.
Either way you should now be back in Limbo, able to reflect on the experience.
Some features exemplified by the tutorial world:
Some features exemplified by the tutorial world:
- Rooms with custom ability to show details (like looking at the wall in the dark room)
- Hidden or impassable exits until you fulfilled some criterion
@ -72,14 +72,14 @@ Some features exemplified by the tutorial world:
- Object spawning (the weapons in the barrel and the final weapoon is actually randomized)
- Teleporter trap rooms (if you fail the obelisk puzzle)
```sidebar:: Extra Credit
```{sidebar} Extra Credit
If you have previous programming experience (or after you have gone
through this Starter tutorial) it may be instructive to dig a little deeper into the Tutorial-world
code to learn how it achieves what it does. The code is heavily documented.
If you have previous programming experience (or after you have gone
through this Starter tutorial) it may be instructive to dig a little deeper into the Tutorial-world
code to learn how it achieves what it does. The code is heavily documented.
You can find all the code in `evennia/contrib/tutorial_world <../../api/evennia.contrib.tutorial_world.html>`_,
the build-script is `here <https://github.com/evennia/evennia/blob/master/evennia/contrib/tutorial_world/build.ev>`_.
When reading and learning from the code, however, keep in mind that *Tutorial World* was created with a very
specific goal in mind: to install easily and to not permanently modify the rest of the server. It therefore
@ -87,7 +87,7 @@ Some features exemplified by the tutorial world:
you will usually need to worry about when making your own game.
```
Quite a lot of stuff crammed in such a small area!
Quite a lot of stuff crammed in such a small area!
## Uninstall the tutorial world