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

View file

@ -1,55 +1,55 @@
# On Planning a Game
Last lesson we asked ourselves some questions about our motivation. In this one we'll present
some more technical questions to consider. In the next lesson we'll answer them for the sake of
Last lesson we asked ourselves some questions about our motivation. In this one we'll present
some more technical questions to consider. In the next lesson we'll answer them for the sake of
our tutorial game.
Note that the suggestions on this page are just that - suggestions. Also, they are primarily aimed at a lone
hobby designer or a small team developing a game in their free time.
Note that the suggestions on this page are just that - suggestions. Also, they are primarily aimed at a lone
hobby designer or a small team developing a game in their free time.
```important::
```{important}
Your first all overshadowing goal is to beat the odds and get **something** out the door!
Even if it's a scaled-down version of your dream game, lacking many "must-have" features!
Your first all overshadowing goal is to beat the odds and get **something** out the door!
Even if it's a scaled-down version of your dream game, lacking many "must-have" features!
```
Remember: *99.99999% of all great game ideas never lead to a game*. Especially not to an online
game that people can actually play and enjoy. It's better to get your game out there and expand on it
game that people can actually play and enjoy. It's better to get your game out there and expand on it
later than to code in isolation until you burn out, lose interest or your hard drive crashes.
- Keep the scope of your initial release down. Way down.
- Keep the scope of your initial release down. Way down.
- Start small, with an eye towards expansions later, after first release.
- If the suggestions here seems boring or a chore to you, do it your way instead. Everyone's different.
- Keep having _fun_. You must keep your motivation up, whichever way works for _you_.
- If the suggestions here seems boring or a chore to you, do it your way instead. Everyone's different.
- Keep having _fun_. You must keep your motivation up, whichever way works for _you_.
## The steps
## The steps
Here are the rough steps towards your goal.
1. Planning
1. Planning
2. Coding + Gradually building a tech-demo
3. Building the actual game world
4. Release
5. Celebrate
## Planning
## Planning
You need to have at least a rough idea about what you want to create. Some like a lot of planning, others
do it more seat-of-the-pants style. Regardless, while _some_ planning is always good to do, it's common
to have your plans change on you as you create your code prototypes. So don't get _too_ bogged down in
You need to have at least a rough idea about what you want to create. Some like a lot of planning, others
do it more seat-of-the-pants style. Regardless, while _some_ planning is always good to do, it's common
to have your plans change on you as you create your code prototypes. So don't get _too_ bogged down in
the details out of the gate.
Many prospective game developers are very good at *parts* of this process, namely in defining what their
world is "about": The theme, the world concept, cool monsters and so on. Such things are very important. But
unfortunately, they are not enough to make your game. You need to figure out how to accomplish your ideas in
Many prospective game developers are very good at *parts* of this process, namely in defining what their
world is "about": The theme, the world concept, cool monsters and so on. Such things are very important. But
unfortunately, they are not enough to make your game. You need to figure out how to accomplish your ideas in
Evennia.
Below are some questions to get you going. In the next lesson we will try to answer them for our particular
tutorial game. There are of course many more questions you could be asking yourself.
### Administration
### Administration
- Should your game rules be enforced by coded systems or by human game masters?
- What is the staff hierarchy in your game? Is vanilla Evennia roles enough or do you need something else?
@ -57,21 +57,21 @@ tutorial game. There are of course many more questions you could be asking yours
### Building
- How will the world be built? Traditionally (from in-game with build-commands) or externally (by batchcmds/code
or directly with custom code)?
- How will the world be built? Traditionally (from in-game with build-commands) or externally (by batchcmds/code
or directly with custom code)?
- Can only privileged Builders create things or should regular players also have limited build-capability?
### Systems
- Do you base your game off an existing RPG system or make up your own?
- What are the game mechanics? How do you decide if an action succeeds or fails?
- What are the game mechanics? How do you decide if an action succeeds or fails?
- Does the flow of time matter in your game - does night and day change? What about seasons?
- Do you want changing, global weather or should weather just be set manually in roleplay?
- Do you want changing, global weather or should weather just be set manually in roleplay?
- Do you want a coded world-economy or just a simple barter system? Or no formal economy at all?
- Do you have concepts like reputation and influence?
- Will your characters be known by their name or only by their physical appearance?
- Do you have concepts like reputation and influence?
- Will your characters be known by their name or only by their physical appearance?
### Rooms
### Rooms
- Is a simple room description enough or should the description be able to change (such as with time, by
light conditions, weather or season)?
@ -91,60 +91,60 @@ created on demand?
- Can you fight with a chair or a flower or must you use a specific 'weapon' kind of thing?
- Will characters be able to craft new objects?
- Should mobs/NPCs have some sort of AI?
- Are NPCs and mobs different entities? How do they differ?
- Should there be NPCs giving quests? If so, how do you track Quest status?
- Are NPCs and mobs different entities? How do they differ?
- Should there be NPCs giving quests? If so, how do you track Quest status?
### Characters
- Can players have more than one Character active at a time or are they allowed to multi-play?
- How does the character-generation work? Walk from room-to-room? A menu?
- How does the character-generation work? Walk from room-to-room? A menu?
- How do you implement different "classes" or "races"? Are they separate types of objects or do you
simply load different stats on a basic object depending on what the Player wants?
- If a Character can hide in a room, what skill will decide if they are detected?
- What does the skill tree look like? Can a Character gain experience to improve? By killing
enemies? Solving quests? By roleplaying?
- May player-characters attack each other (PvP)?
- What are the penalties of defeat? Permanent death? Quick respawn? Time in prison?
- What are the penalties of defeat? Permanent death? Quick respawn? Time in prison?
A MUD's a lot more involved than you would think and these things hang together in a complex web. It
can easily become overwhelming and it's tempting to want *all* functionality right out of the door.
Try to identify the basic things that "make" your game and focus *only* on them for your first
release. Make a list. Keep future expansions in mind but limit yourself.
## Coding and Tech demo
## Coding and Tech demo
This is the actual work of creating the "game" part of your game. As you code and test systems you should
build a little "tech demo" along the way.
This is the actual work of creating the "game" part of your game. As you code and test systems you should
build a little "tech demo" along the way.
```sidebar:: Tech demo
```{sidebar} Tech demo
With "tech demo" we mean a small example of your code in-action: A room with a mob,
a way to jump into and test character-creation etc. The tech demo need not be pretty, it's
a way to jump into and test character-creation etc. The tech demo need not be pretty, it's
there to test functionality. It's not the beginning of your game world (unless you find that
to be more fun).
```
Try to avoid going wild with building a huge game world before you have a tech-demo showing off all parts
Try to avoid going wild with building a huge game world before you have a tech-demo showing off all parts
you expect to have in the first version of your game. Otherwise you run the risk of having to redo it all
again.
again.
Evennia tries hard to make the coding easier for you, but there is no way around the fact that if you want
anything but a basic chat room you *will* have to bite the bullet and code your game (or find a coder willing
Evennia tries hard to make the coding easier for you, but there is no way around the fact that if you want
anything but a basic chat room you *will* have to bite the bullet and code your game (or find a coder willing
to do it for you).
> Even if you won't code anything yourself, as a designer you need to at least understand the basic
paradigms and components of Evennia. It's recommended you look over the rest of this Beginner Tutorial to learn
what tools you have available.
what tools you have available.
During Coding you look back at the things you wanted during the **Planning** phase and try to
implement them. Don't be shy to update your plans if you find things easier/harder than you thought.
The earlier you revise problems, the easier they will be to fix.
A good idea is to host your code online using _version control_. Github.com offers free Private repos
A good idea is to host your code online using _version control_. Github.com offers free Private repos
these days if you don't want the world to learn your secrets. Not only version control
make it easy for your team to collaborate, it also means
your work is backed up at all times. The page on [Version Control](../../../Coding/Version-Control)
your work is backed up at all times. The page on [Version Control](../../../Coding/Version-Control.md)
will help you to setting up a sane developer environment with proper version control.
## World Building
@ -154,17 +154,17 @@ populating the database with a larger, thematic world. Too many would-be develop
stage too soon (skipping the **Coding** or even **Planning** stages). What if the rooms you build
now doesn't include all the nice weather messages the code grows to support? Or the way you store
data changes under the hood? Your building work would at best require some rework and at worst you
would have to redo the whole thing. You could be in for a *lot* of unnecessary work if you build stuff
would have to redo the whole thing. You could be in for a *lot* of unnecessary work if you build stuff
en masse without having the underlying code systems in some reasonable shape first.
So before starting to build, the "game" bit (**Coding** + **Testing**) should be more or less
**complete**, *at least to the level of your initial release*.
Make sure it is clear to yourself and your eventual builders just which parts of the world you want
for your initial release. Establish for everyone which style, quality and level of detail you expect.
Make sure it is clear to yourself and your eventual builders just which parts of the world you want
for your initial release. Establish for everyone which style, quality and level of detail you expect.
Your goal should *not* be to complete your entire world in one go. You want just enough to make the
game's "feel" come across. You want a minimal but functioning world where the intended game play can
Your goal should *not* be to complete your entire world in one go. You want just enough to make the
game's "feel" come across. You want a minimal but functioning world where the intended game play can
be tested and roughly balanced. You can always add new areas later.
During building you get free and extensive testing of whatever custom build commands and systems you
@ -176,18 +176,18 @@ to this feedback.
## Alpha Release
As mentioned, don't hold onto your world more than necessary. *Get it out there* with a huge *Alpha*
flag and let people try it!
flag and let people try it!
Call upon your alpha-players to try everything - they *will* find ways to break your game in ways that
Call upon your alpha-players to try everything - they *will* find ways to break your game in ways that
you never could have imagined. In Alpha you might be best off to
focus on inviting friends and maybe other MUD developers, people who you can pester to give proper
feedback and bug reports (there *will* be bugs, there is no way around it).
feedback and bug reports (there *will* be bugs, there is no way around it).
Follow the quick instructions for [Online Setup](../../../Setup/Online-Setup) to make your
game visible online.
Follow the quick instructions for [Online Setup](../../../Setup/Online-Setup.md) to make your
game visible online.
If you hadn't already, make sure to put up your game on the
[Evennia game index](http://games.evennia.com/) so people know it's in the works (actually, even
If you hadn't already, make sure to put up your game on the
[Evennia game index](http://games.evennia.com/) so people know it's in the works (actually, even
pre-alpha games are allowed in the index so don't be shy)!
## Beta Release/Perpetual Beta
@ -198,12 +198,12 @@ Once things stabilize in Alpha you can move to *Beta* and let more people in. Ma
features get implemented or Players come with suggestions. As the game designer it is now up to you
to gradually perfect your vision.
## Congratulate yourself!
## Congratulate yourself!
You are worthy of a celebration since at this point you have joined the small, exclusive crowd who
have made their dream game a reality!
## Planning our tutorial game
In the next lesson we'll make use of these general points and try to plan out our tutorial game.
In the next lesson we'll make use of these general points and try to plan out our tutorial game.

View file

@ -8,7 +8,7 @@ That said, Evennia _does_ offer some more game-opinionated _optional_ stuff. The
and is an ever-growing treasure trove of code snippets, concepts and even full systems you can pick and choose
from to use, tweak or take inspiration from when you make your game.
The [Contrib overview](../../../Contribs/Contrib-Overview) page gives the full list of the current roster of contributions. On
The [Contrib overview](../../../Contribs/Contrib-Overview.md) page gives the full list of the current roster of contributions. On
this page we will review a few contribs we will make use of for our game. We will do the actual installation
of them when we start coding in the next part of this tutorial series. While we will introduce them here, you
are wise to read their doc-strings yourself for the details.
@ -26,7 +26,7 @@ This is the things we know we need:
## Barter contrib
[source](api:evennia.contrib.barter)
[source](../../../api/evennia.contrib.barter.md)
Reviewing this contrib suggests that it allows for safe trading between two parties. The basic principle
is that the parties puts up the stuff they want to sell and the system will guarantee that these systems are
@ -57,7 +57,7 @@ things than boring gold coin.
## Character generation contrib
[source](api:evennia.contrib.chargen)
[source](../../../api/evennia.contrib.chargen.md)
This contrib is an example module for creating characters. Since we will be using `MULTISESSION_MODE=3` we will
get a selection screen like this automatically. We also plan to use a proper menu to build our character, so
@ -65,7 +65,7 @@ we will _not_ be using this contrib.
## Clothing contrib
[source](api:evennia.contrib.clothing)
[source](../../../api/evennia.contrib.clothing.md)
This contrib provides a full system primarily aimed at wearing clothes, but it could also work for armor. You wear
an object in a particular location and this will then be reflected in your character's description. You can
@ -80,7 +80,7 @@ for things like armor. It's a good contrib to build from though, so that's what
## Dice contrib
[source](api:evennia.contrib.dice)
[source](../../../api/evennia.contrib.dice.md)
The dice contrib presents a general dice roller to use in game.
@ -102,7 +102,7 @@ or play a game, we will not need it for the core of our game.
## Extended room contrib
[source](api:evennia.contrib.extended_room)
[source](../../../api/evennia.contrib.extended_room.md)
This is a custom Room typeclass that changes its description based on time of day and season.
@ -122,7 +122,7 @@ game, why not!
## RP-System contrib
[source](api:evennia.contrib.rpsystem)
[source](../../../api/evennia.contrib.rpsystem.md)
This contrib adds a full roleplaying subsystem to your game. It gives every character a "short-description"
(sdesc) that is what people will see when first meeting them. Let's say Tom has an sdesc "A tall man" and
@ -159,20 +159,20 @@ You can also wear a mask to hide your identity; your sdesc will then be changed
like `a person with a mask`.
The RPSystem gives a lot of roleplaying power out of the box, so we will add it. There is also a separate
[rplanguage](api:evennia.contrib.rplanguage) module that integrates with the spoken words in your emotes and garbles them if you don't understand
[rplanguage](../../../api/evennia.contrib.rplanguage.md) module that integrates with the spoken words in your emotes and garbles them if you don't understand
the language spoken. In order to restrict the scope we will not include languages for the tutorial game.
## Talking NPC contrib
[source](api:evennia.contrib.talking_npc)
[source](../../../api/evennia.contrib.talking_npc.md)
This exemplifies an NPC with a menu-driven dialogue tree. We will not use this contrib explicitly, but it's
good as inspiration for how we'll do quest-givers later.
## Traits contrib
[source](api:evennia.contrib.traits)
[source](../../../api/evennia.contrib.traits.md)
An issue with dealing with roleplaying attributes like strength, dexterity, or skills like hunting, sword etc
is how to keep track of the values in the moment. Your strength may temporarily be buffed by a strength-potion.
@ -226,7 +226,7 @@ Traits will be very practical to use for our character sheets.
## Turnbattle contrib
[source](api:evennia.contrib.turnbattle)
[source](../../../api/evennia.contrib.turnbattle.md)
This contrib consists of several implementations of a turn-based combat system, divivided into complexity:

View file

@ -1,53 +1,53 @@
# Planning our tutorial game
Using the general plan from last lesson we'll now establish what kind of game we want to create for this tutorial.
Remembering that we need to keep the scope down, let's establish some parameters.
Note that for your own
game you don't _need_ to agree/adopt any of these. Many game-types need more or much less than this.
Remembering that we need to keep the scope down, let's establish some parameters.
Note that for your own
game you don't _need_ to agree/adopt any of these. Many game-types need more or much less than this.
But this makes for good, instructive examples.
- To have something to refer to rather than just saying "our tutorial game" over and over, we'll
- To have something to refer to rather than just saying "our tutorial game" over and over, we'll
name it ... _EvAdventure_.
- We want EvAdventure be a small game we can play ourselves for fun, but which could in principle be expanded
- We want EvAdventure be a small game we can play ourselves for fun, but which could in principle be expanded
to something more later.
- Let's go with a fantasy theme, it's well understood.
- We'll use some existing, simple RPG system.
- We want to be able to create and customize a character of our own.
- We want the tools to roleplay with other players.
- We'll use some existing, simple RPG system.
- We want to be able to create and customize a character of our own.
- We want the tools to roleplay with other players.
- We don't want to have to rely on a Game master to resolve things, but will rely on code for skill resolution
and combat.
- We want monsters to fight and NPCs we can talk to. So some sort of AI.
- We want to be able to buy and sell stuff, both with NPCs and other players.
- We want monsters to fight and NPCs we can talk to. So some sort of AI.
- We want to be able to buy and sell stuff, both with NPCs and other players.
- We want some sort of crafting system.
- We want some sort of quest system.
- We want some sort of quest system.
Let's answer the questions from the previous lesson and discuss some of the possibilities.
Let's answer the questions from the previous lesson and discuss some of the possibilities.
### Administration
## Administration
#### Should your game rules be enforced by coded systems by human game masters?
### Should your game rules be enforced by coded systems by human game masters?
Generally, the more work you expect human staffers/GMs to do, the less your code needs to work. To
support GMs you'd need to design commands to support GM-specific actions and the type of game-mastering
you want them to do. You may need to expand communication channels so you can easily
Generally, the more work you expect human staffers/GMs to do, the less your code needs to work. To
support GMs you'd need to design commands to support GM-specific actions and the type of game-mastering
you want them to do. You may need to expand communication channels so you can easily
talk to groups people in private and split off gaming groups from each other. RPG rules could be as simple
as the GM sitting with the rule books and using a dice-roller for visibility.
GM:ing is work-intensive however, and even the most skilled and enthusiastic GM can't be awake all hours
GM:ing is work-intensive however, and even the most skilled and enthusiastic GM can't be awake all hours
of the day to serve an international player base. The computer never needs sleep, so having the ability for
players to "self-serve" their RP itch when no GMs are around is a good idea even for the most GM-heavy games.
On the other side of the spectrum are games with no GMs at all; all gameplay are driven either by the computer
or by the interactions between players. Such games still need an active staff, but nowhere as much active
or by the interactions between players. Such games still need an active staff, but nowhere as much active
involvement. Allowing for solo-play with the computer also allows players to have fun when the number of active
players is low.
players is low.
We want EvAdventure to work entirely without depending on human GMs. That said, there'd be nothing
stopping a GM from stepping in and run an adventure for some players should they want to.
We want EvAdventure to work entirely without depending on human GMs. That said, there'd be nothing
stopping a GM from stepping in and run an adventure for some players should they want to.
#### What is the staff hierarchy in your game? Is vanilla Evennia roles enough or do you need something else?
### What is the staff hierarchy in your game? Is vanilla Evennia roles enough or do you need something else?
The default hierarchy is
The default hierarchy is
- `Player` - regular players
- `Player Helper` - can create/edit help entries
@ -58,368 +58,368 @@ The default hierarchy is
There is also the _superuser_, the "owner" of the game you create when you first set up your database. This user
goes outside the regular hierarchy and should usually only.
We are okay with keeping this structure for our game.
We are okay with keeping this structure for our game.
#### Should players be able to post out-of-characters on channels and via other means like bulletin-boards?
Evennia's _Channels_ are by default only available between _Accounts_. That is, for players to communicate with each
### Should players be able to post out-of-characters on channels and via other means like bulletin-boards?
Evennia's _Channels_ are by default only available between _Accounts_. That is, for players to communicate with each
other. By default, the `public` channel is created for general discourse.
Channels are logged to a file and when you are coming back to the game you can view the history of a channel
in case you missed something.
in case you missed something.
> public Hello world!
[Public] MyName: Hello world!
[Public] MyName: Hello world!
But Channels can also be set up to work between Characters instead of Accounts. This would mean the channels
would have an in-game meaning:
would have an in-game meaning:
- Members of a guild could be linked telepathically.
- Members of a guild could be linked telepathically.
- Survivors of the apocalypse can communicate over walkie-talkies.
- Radio stations you can tune into or have to discover.
- Radio stations you can tune into or have to discover.
_Bulletin boards_ are a sort of in-game forum where posts are made publicly or privately. Contrary to a channel,
the messages are usually stored and are grouped into topics with replies. Evennia has no default bulletin-board
system.
system.
In EvAdventure we will just use the default inter-account channels. We will also not be implementing any
bulletin boards.
In EvAdventure we will just use the default inter-account channels. We will also not be implementing any
bulletin boards.
### Building
## Building
#### How will the world be built?
There are two main ways to handle this:
### How will the world be built?
There are two main ways to handle this:
- Traditionally, from in-game with build-commands: This means builders creating content in their game
client. This has the advantage of not requiring Python skills nor server access. This can often be a quite
intuitive way to build since you are sort-of walking around in your creation as you build it. However, the
developer (you) must make sure to provide build-commands that are flexible enough for builders to be able to
create the content you want for your game.
- Externally (by batchcmds): Evennia's `batchcmd` takes a text file with Evennia Commands and executes them
in sequence. This allows the build process to be repeated and applied quickly to a new database during development.
client. This has the advantage of not requiring Python skills nor server access. This can often be a quite
intuitive way to build since you are sort-of walking around in your creation as you build it. However, the
developer (you) must make sure to provide build-commands that are flexible enough for builders to be able to
create the content you want for your game.
- Externally (by batchcmds): Evennia's `batchcmd` takes a text file with Evennia Commands and executes them
in sequence. This allows the build process to be repeated and applied quickly to a new database during development.
It also allows builders to use proper text-editing tools rather than writing things line-by-line in their clients.
The drawback is that for their changes to go live they either need server access or they need to send their
batchcode to the game administrator so they can apply the changes. Or use version control.
- Externally (with batchcode or custom code): This is the "professional game development" approach. This gives the
builders maximum power by creating the content in Python using Evennia primitives. The `batchcode` processor
allows Evennia to apply and re-apply build-scripts that are raw Python modules. Again, this would require the
The drawback is that for their changes to go live they either need server access or they need to send their
batchcode to the game administrator so they can apply the changes. Or use version control.
- Externally (with batchcode or custom code): This is the "professional game development" approach. This gives the
builders maximum power by creating the content in Python using Evennia primitives. The `batchcode` processor
allows Evennia to apply and re-apply build-scripts that are raw Python modules. Again, this would require the
builder to have server access or to use version control to share their work with the rest of the development team.
In this tutorial, we will show examples of all these ways, but since we don't have a team of builders we'll
build the brunt of things using Evennia's Batchcode system.
In this tutorial, we will show examples of all these ways, but since we don't have a team of builders we'll
build the brunt of things using Evennia's Batchcode system.
#### Can only privileged Builders create things or should regular players also have limited build-capability?
### Can only privileged Builders create things or should regular players also have limited build-capability?
In some game styles, players have the ability to create objects and even script them. While giving regular users
the ability to create objects with in-built commands is easy and safe, actual code-creation (aka _softcode_ ) is
not something Evennia supports natively. Regular, untrusted users should never be allowed to execute raw Python
code (such as what you can do with the `py` command). You can
[read more about Evennia's stance on softcode here](../../../Concepts/Soft-Code). If you want users to do limited scripting,
it's suggested that this is accomplished by adding more powerful build-commands for them to use.
the ability to create objects with in-built commands is easy and safe, actual code-creation (aka _softcode_ ) is
not something Evennia supports natively. Regular, untrusted users should never be allowed to execute raw Python
code (such as what you can do with the `py` command). You can
[read more about Evennia's stance on softcode here](../../../Concepts/Soft-Code.md). If you want users to do limited scripting,
it's suggested that this is accomplished by adding more powerful build-commands for them to use.
For our tutorial-game, we will only allow privileged builders to modify the world. The exception is crafting,
For our tutorial-game, we will only allow privileged builders to modify the world. The exception is crafting,
which we will limit to repairing broken items by combining them with other repair-related items.
### Systems
## Systems
#### Do you base your game off an existing RPG system or make up your own?
### Do you base your game off an existing RPG system or make up your own?
We will make use of [Open Adventure](http://www.geekguild.com/openadventure/), a simple 'old school' RPG-system
that is available for free under the Creative Commons license. We'll only use a subset of the rules from
We will make use of [Open Adventure](http://www.geekguild.com/openadventure/), a simple 'old school' RPG-system
that is available for free under the Creative Commons license. We'll only use a subset of the rules from
the blue "basic" book. For the sake of keeping down the length of this tutorial we will limit what features
we will include:
we will include:
- Only two 'archetypes' (classes) - Arcanist (wizard) and Warrior, these are examples of two different play
styles.
- Two races only (dwarves and elves), to show off how to implement races and race bonuses.
- No extra features of the races/archetypes such as foci and special feats. While these are good for fleshing
- No extra features of the races/archetypes such as foci and special feats. While these are good for fleshing
out a character, these will work the same as other bonuses and are thus not that instructive.
- We will add only a small number of items/weapons from the Open Adventure rulebook to show how it's done.
- We will add only a small number of items/weapons from the Open Adventure rulebook to show how it's done.
#### What are the game mechanics? How do you decide if an action succeeds or fails?
### What are the game mechanics? How do you decide if an action succeeds or fails?
Open Adventure's conflict resolution is based on adding a trait (such as Strength) with a random number in
order to beat a target. We will emulate this in code.
Open Adventure's conflict resolution is based on adding a trait (such as Strength) with a random number in
order to beat a target. We will emulate this in code.
Having a "skill" means getting a bonus to that roll for a more narrow action.
Having a "skill" means getting a bonus to that roll for a more narrow action.
Since the computer will need to know exactly what those skills are, we will add them more explicitly than
in the rules, but we will only add the minimum to show off the functionality we need.
#### Does the flow of time matter in your game - does night and day change? What about seasons?
### Does the flow of time matter in your game - does night and day change? What about seasons?
Most commonly, game-time runs faster than real-world time. There are
a few advantages with this:
Most commonly, game-time runs faster than real-world time. There are
a few advantages with this:
- Unlike in a single-player game, you can't fast-forward time in a multiplayer game if you are waiting for
something, like NPC shops opening.
- Unlike in a single-player game, you can't fast-forward time in a multiplayer game if you are waiting for
something, like NPC shops opening.
- Healing and other things that we know takes time will go faster while still being reasonably 'realistic'.
The main drawback is for games with slower roleplay pace. While you are having a thoughtful roleplaying scene
over dinner, the game world reports that two days have passed. Having a slower game time than real-time is
a less common, but possible solution for such games.
over dinner, the game world reports that two days have passed. Having a slower game time than real-time is
a less common, but possible solution for such games.
It is however _not_ recommended to let game-time exactly equal the speed of real time. The reason for this
is that people will join your game from all around the world, and they will often only be able to play at
particular times of their day. With a game-time drifting relative real-time, everyone will eventually be
able to experience both day and night in the game.
It is however _not_ recommended to let game-time exactly equal the speed of real time. The reason for this
is that people will join your game from all around the world, and they will often only be able to play at
particular times of their day. With a game-time drifting relative real-time, everyone will eventually be
able to experience both day and night in the game.
For this tutorial-game we will go with Evennia's default, which is that the game-time runs two times faster
than real time.
#### Do you want changing, global weather or should weather just be set manually in roleplay?
### Do you want changing, global weather or should weather just be set manually in roleplay?
A weather system is a good example of a game-global system that affects a subset of game entities
(outdoor rooms). We will not be doing any advanced weather simulation, but we'll show how to do
random weather changes happening across the game world.
A weather system is a good example of a game-global system that affects a subset of game entities
(outdoor rooms). We will not be doing any advanced weather simulation, but we'll show how to do
random weather changes happening across the game world.
#### Do you want a coded world-economy or just a simple barter system? Or no formal economy at all?
### Do you want a coded world-economy or just a simple barter system? Or no formal economy at all?
We will allow for money and barter/trade between NPCs/Players and Player/Player, but will not care about
inflation. A real economic simulation could do things like modify shop prices based on supply and demand.
We will allow for money and barter/trade between NPCs/Players and Player/Player, but will not care about
inflation. A real economic simulation could do things like modify shop prices based on supply and demand.
We will not go down that rabbit hole.
#### Do you have concepts like reputation and influence?
These are useful things for a more social-interaction heavy game. We will not include them for this
tutorial however.
### Do you have concepts like reputation and influence?
#### Will your characters be known by their name or only by their physical appearance?
These are useful things for a more social-interaction heavy game. We will not include them for this
tutorial however.
### Will your characters be known by their name or only by their physical appearance?
This is a common thing in RP-heavy games. Others will only see you as "The tall woman" until you
introduce yourself and they 'recognize' you with a name. Linked to this is the concept of more complex
emoting and posing.
emoting and posing.
Adding such a system from scratch is complex and way beyond the scope of this tutorial. However,
there is an existing Evennia contrib that adds all of this functionality and more, so we will
Adding such a system from scratch is complex and way beyond the scope of this tutorial. However,
there is an existing Evennia contrib that adds all of this functionality and more, so we will
include that and explain briefly how it works.
### Rooms
## Rooms
#### Is a simple room description enough or should the description be able to change?
### Is a simple room description enough or should the description be able to change?
Changing room descriptions for day and night, winder and summer is actually quite easy to do, but looks
very impressive. We happen to know there is also a contrib that helps with this, so we'll show how to
include that.
Changing room descriptions for day and night, winder and summer is actually quite easy to do, but looks
very impressive. We happen to know there is also a contrib that helps with this, so we'll show how to
include that.
#### Should the room have different statuses?
### Should the room have different statuses?
We will have different weather in outdoor rooms, but this will not have any gameplay effect - bow strings
will not get wet and fireballs will not fizzle if it rains.
will not get wet and fireballs will not fizzle if it rains.
#### Can objects be hidden in the room? Can a person hide in the room?
### Can objects be hidden in the room? Can a person hide in the room?
We will not model hiding and stealth. This will be a game of honorable face-to-face conflict.
### Objects
## Objects
#### How numerous are your objects? Do you want large loot-lists or are objects just role playing props?
### How numerous are your objects? Do you want large loot-lists or are objects just role playing props?
Since we are not going for a pure freeform RPG here, we will want objects with properties, like weapons
and potions and such. Monsters should drop loot even though our list of objects will not be huge.
Since we are not going for a pure freeform RPG here, we will want objects with properties, like weapons
and potions and such. Monsters should drop loot even though our list of objects will not be huge.
#### Is each coin a separate object or do you just store a bank account value?
### Is each coin a separate object or do you just store a bank account value?
Since we will use bartering, placing coin objects on one side of the barter makes for a simple way to
Since we will use bartering, placing coin objects on one side of the barter makes for a simple way to
handle payments. So we will use coins as-objects.
#### Do multiple similar objects form stacks and how are those stacks handled in that case?
### Do multiple similar objects form stacks and how are those stacks handled in that case?
Since we'll use coins, it's practical to have them and other items stack together. While Evennia does not
do this natively, we will make use of a contrib for this.
Since we'll use coins, it's practical to have them and other items stack together. While Evennia does not
do this natively, we will make use of a contrib for this.
#### Does an object have weight or volume (so you cannot carry an infinite amount of them)?
### Does an object have weight or volume (so you cannot carry an infinite amount of them)?
Limiting carrying weight is one way to stop players from hoarding. It also makes it more important
for players to pick only the equipment they need. Carrying limits can easily come across as
annoying to players though, so one needs to be careful with it.
Limiting carrying weight is one way to stop players from hoarding. It also makes it more important
for players to pick only the equipment they need. Carrying limits can easily come across as
annoying to players though, so one needs to be careful with it.
Open Adventure rules include weight limits, so we will include them.
Open Adventure rules include weight limits, so we will include them.
#### Can objects be broken? Can they be repaired?
### Can objects be broken? Can they be repaired?
Item breakage is very useful for a game economy; breaking weapons adds tactical considerations (if it's not
too common, then it becomes annoying) and repairing things gives work for crafting players.
Item breakage is very useful for a game economy; breaking weapons adds tactical considerations (if it's not
too common, then it becomes annoying) and repairing things gives work for crafting players.
We wanted a crafting system, so this is what we will limit it to - repairing items using some sort
of raw materials.
We wanted a crafting system, so this is what we will limit it to - repairing items using some sort
of raw materials.
#### Can you fight with a chair or a flower or must you use a special 'weapon' kind of thing?
### Can you fight with a chair or a flower or must you use a special 'weapon' kind of thing?
Traditionally, only 'weapons' could be used to fight with. In the past this was a useful
simplification, but with Python classes and inheritance, it's not actually more work to just
let all items in game work as a weapon in a pinch.
Traditionally, only 'weapons' could be used to fight with. In the past this was a useful
simplification, but with Python classes and inheritance, it's not actually more work to just
let all items in game work as a weapon in a pinch.
So for our game we will let a character use any item they want as a weapon. The difference will
be that non-weapon items will do less damage and also break and become unusable much quicker.
So for our game we will let a character use any item they want as a weapon. The difference will
be that non-weapon items will do less damage and also break and become unusable much quicker.
#### Will characters be able to craft new objects?
### Will characters be able to craft new objects?
Crafting is a common feature in multiplayer games. In code it usually means using a skill-check
to combine base ingredients from a fixed recipe in order to create a new item. The classic
example is to combine _leather straps_, a _hilt_, a _pommel_ and a _blade_ to make a new _sword_.
A full-fledged crafting system could require multiple levels of crafting, including having to mine
for ore or cut down trees for wood.
Crafting is a common feature in multiplayer games. In code it usually means using a skill-check
to combine base ingredients from a fixed recipe in order to create a new item. The classic
example is to combine _leather straps_, a _hilt_, a _pommel_ and a _blade_ to make a new _sword_.
A full-fledged crafting system could require multiple levels of crafting, including having to mine
for ore or cut down trees for wood.
In our case we will limit our crafting to repairing broken items. To show how it's done, we will require
extra items (a recipe) in order to facilitate the repairs.
extra items (a recipe) in order to facilitate the repairs.
#### Should mobs/NPCs have some sort of AI?
### Should mobs/NPCs have some sort of AI?
A rule of adding Artificial Intelligence is that with today's technology you should not hope to fool
A rule of adding Artificial Intelligence is that with today's technology you should not hope to fool
anyone with it anytime soon. Unless you have a side-gig as an AI researcher, users will likely
not notice any practical difference between a simple state-machine and you spending a lot of time learning
how to train a neural net.
how to train a neural net.
For this tutorial, we will show how to add a simple state-machine for monsters. NPCs will only be
shop-keepers and quest-gives so they won't need any real AI to speak of.
For this tutorial, we will show how to add a simple state-machine for monsters. NPCs will only be
shop-keepers and quest-gives so they won't need any real AI to speak of.
#### Are NPCs and mobs different entities? How do they differ?
### Are NPCs and mobs different entities? How do they differ?
"Mobs" or "mobiles" are things that move around. This is traditionally monsters you can fight with, but could
"Mobs" or "mobiles" are things that move around. This is traditionally monsters you can fight with, but could
also be city guards or the baker going to chat with the neighbor. Back in the day, they were often fundamentally
different these days it's often easier to just make NPCs and mobs essentially the same thing.
different these days it's often easier to just make NPCs and mobs essentially the same thing.
In EvAdventure, both Monsters and NPCs will be the same type of thing; A monster could give you a quest
and an NPC might fight you as a mob as well as trade with you.
and an NPC might fight you as a mob as well as trade with you.
#### _Should there be NPCs giving quests? If so, how do you track Quest status?
### _Should there be NPCs giving quests? If so, how do you track Quest status?
We will design a simple quest system to track the status of ongoing quests.
We will design a simple quest system to track the status of ongoing quests.
### Characters
## Characters
#### Can players have more than one Character active at a time or are they allowed to multi-play?
### Can players have more than one Character active at a time or are they allowed to multi-play?
Since Evennia differentiates between `Sessions` (the client-connection to the game), `Accounts`
and `Character`s, it natively supports multi-play. This is controlled by the `MULTISESSION_MODE`
setting, which has a value from `0` (default) to `3`.
Since Evennia differentiates between `Sessions` (the client-connection to the game), `Accounts`
and `Character`s, it natively supports multi-play. This is controlled by the `MULTISESSION_MODE`
setting, which has a value from `0` (default) to `3`.
- `0`- One Character per Account and one Session per Account. This means that if you login to the same
- `0`- One Character per Account and one Session per Account. This means that if you login to the same
account from another client you'll be disconnected from the first. When creating a new account, a Character
will be auto-created with the same name as your Account. This is default mode and mimics legacy code bases
which had no separation between Account and Character.
- `1` - One Character per Account, multiple Sessions per Account. So you can connect simultaneously from
which had no separation between Account and Character.
- `1` - One Character per Account, multiple Sessions per Account. So you can connect simultaneously from
multiple clients and see the same output in all of them.
- `2` - Multiple Characters per Account, one Session per Character. This will not auto-create a same-named
Character for you, instead you get to create/choose between a number of Characters up to a max limit given by
- `2` - Multiple Characters per Account, one Session per Character. This will not auto-create a same-named
Character for you, instead you get to create/choose between a number of Characters up to a max limit given by
the `MAX_NR_CHARACTERS` setting (default 1). You can play them all simultaneously if you have multiple clients
open, but only one client per Character.
- `3` - Multiple Characters per Account, Multiple Sessions per Character. This is like mode 2, except players
can control each Character from multiple clients, seeing the same output from each Character.
can control each Character from multiple clients, seeing the same output from each Character.
We will go with a multi-role game, so we will use `MULTISESSION_MODE=3` for this tutorial.
We will go with a multi-role game, so we will use `MULTISESSION_MODE=3` for this tutorial.
#### How does the character-generation work?
### How does the character-generation work?
There are a few common ways to do character generation:
- Rooms. This is the traditional way. Each room's description tells you what command to use to modify
- Rooms. This is the traditional way. Each room's description tells you what command to use to modify
your character. When you are done you move to the next room. Only use this if you have another reason for
using a room, like having a training dummy to test skills on, for example.
- A Menu. The Evennia _EvMenu_ system allows you to code very flexible in-game menus without needing to walk
- A Menu. The Evennia _EvMenu_ system allows you to code very flexible in-game menus without needing to walk
between rooms. You can both have a step-by-step menu (a 'wizard') or allow the user to jump between the
steps as they please. This tends to be a lot easier for newcomers to understand since it doesn't require
steps as they please. This tends to be a lot easier for newcomers to understand since it doesn't require
using custom commands they will likely never use again after this.
- Questions. A fun way to build a character is to answer a series of questions. This is usually implemented
with a sequential menu.
with a sequential menu.
For the tutorial we will use a menu to let the user modify each section of their character sheet in any order
until they are happy.
#### How do you implement different "classes" or "races"?
until they are happy.
The way classes and races work in most RPGs (as well as in OpenAdventure) is that they act as static 'templates'
that inform which bonuses and special abilities you have. This means that all we need to store on the
Character is _which_ class and _which_ race they have; the actual logic can sit in Python code and just
be looked up when we need it.
### How do you implement different "classes" or "races"?
#### If a Character can hide in a room, what skill will decide if they are detected?
The way classes and races work in most RPGs (as well as in OpenAdventure) is that they act as static 'templates'
that inform which bonuses and special abilities you have. This means that all we need to store on the
Character is _which_ class and _which_ race they have; the actual logic can sit in Python code and just
be looked up when we need it.
Hiding means a few things.
### If a Character can hide in a room, what skill will decide if they are detected?
Hiding means a few things.
- The Character should not appear in the room's description / character list
- Others hould not be able to interact with a hidden character. It'd be weird if you could do `attack <name>`
or `look <name>` if the named character is in hiding.
- There must be a way for the person to come out of hiding, and probably for others to search or accidentally
find the person (probably based on skill checks).
- The room will also need to be involved, maybe with some modifier as to how easy it is to hide in the room.
- The room will also need to be involved, maybe with some modifier as to how easy it is to hide in the room.
We will _not_ be including a hide-mechanic in EvAdventure though.
We will _not_ be including a hide-mechanic in EvAdventure though.
#### What does the skill tree look like? Can a Character gain experience to improve? By killing enemies? Solving quests? By roleplaying?
### What does the skill tree look like? Can a Character gain experience to improve? By killing enemies? Solving quests? By roleplaying?
Gaining experience points (XP) and improving one's character is a staple of roleplaying games. There are many
ways to implement this:
- Gaining XP from kills is very common; it's easy to let a monster be 'worth' a certain number of XP and it's
easy to tell when you should gain it.
Gaining experience points (XP) and improving one's character is a staple of roleplaying games. There are many
ways to implement this:
- Gaining XP from kills is very common; it's easy to let a monster be 'worth' a certain number of XP and it's
easy to tell when you should gain it.
- Gaining XP from quests is the same - each quest is 'worth' XP and you get them when completing the test.
- Gaining XP from roleplay is harder to define. Different games have tried a lot of different ways to do this:
- XP from being online - just being online gains you XP. This inflates player numbers but many players may
- XP from being online - just being online gains you XP. This inflates player numbers but many players may
just be lurking and not be actually playing the game at any given time.
- XP from roleplaying scenes - you gain XP according to some algorithm analyzing your emotes for 'quality',
how often you post, how long your emotes are etc.
- XP from roleplaying scenes - you gain XP according to some algorithm analyzing your emotes for 'quality',
how often you post, how long your emotes are etc.
- XP from actions - you gain XP when doing things, anything. Maybe your XP is even specific to each action, so
you gain XP only for running when you run, XP for your axe skill when you fight with an axe etc.
- XP from fails - you only gain XP when failing rolls.
- XP from other players - other players can award you XP for good RP.
For EvAdventure we will use Open Adventure's rules for XP, which will be driven by kills and quest successes.
#### May player-characters attack each other (PvP)?
- XP from fails - you only gain XP when failing rolls.
- XP from other players - other players can award you XP for good RP.
Deciding this affects the style of your entire game. PvP makes for exciting gameplay but it opens a whole new
can of worms when it comes to "fairness". Players will usually accept dying to an overpowered NPC dragon. They
will not be as accepting if they perceive another player is perceived as being overpowered. PvP means that you
have to be very careful to balance the game - all characters does not have to be exactly equal but they should
all be viable to play a fun game with. PvP does not only mean combat though. Players can compete in all sorts of ways, including gaining influence in
a political game or gaining market share when selling their crafted merchandise.
For EvAdventure we will use Open Adventure's rules for XP, which will be driven by kills and quest successes.
### May player-characters attack each other (PvP)?
Deciding this affects the style of your entire game. PvP makes for exciting gameplay but it opens a whole new
can of worms when it comes to "fairness". Players will usually accept dying to an overpowered NPC dragon. They
will not be as accepting if they perceive another player is perceived as being overpowered. PvP means that you
have to be very careful to balance the game - all characters does not have to be exactly equal but they should
all be viable to play a fun game with. PvP does not only mean combat though. Players can compete in all sorts of ways, including gaining influence in
a political game or gaining market share when selling their crafted merchandise.
For the EvAdventure we will support both Player-vs-environment combat and turn-based PvP. We will allow players
to barter with each other (so potentially scam others?) but that's the extent of it. We will focus on showing
off techniques and will not focus on making a balanced game.
off techniques and will not focus on making a balanced game.
#### What are the penalties of defeat? Permanent death? Quick respawn? Time in prison?
### What are the penalties of defeat? Permanent death? Quick respawn? Time in prison?
This is another big decision that strongly affects the mood and style of your game.
Perma-death means that once your character dies, it's gone and you have to make a new one.
Perma-death means that once your character dies, it's gone and you have to make a new one.
- It allows for true heroism. If you genuinely risk losing your character of two years to fight the dragon,
- It allows for true heroism. If you genuinely risk losing your character of two years to fight the dragon,
your triumph is an actual feat.
- It limits the old-timer dominance problem. If long-time players dies occationally, it will open things
up for newcomers.
- It lowers inflation, since the hoarded resources of a dead character can be removed.
- It gives capital punishment genuine discouraging power.
- It's realistic.
up for newcomers.
- It lowers inflation, since the hoarded resources of a dead character can be removed.
- It gives capital punishment genuine discouraging power.
- It's realistic.
Perma-death comes with some severe disadvantages however.
Perma-death comes with some severe disadvantages however.
- It's impopular. Many players will just not play a game where they risk losing their beloved character
just like that.
- It's impopular. Many players will just not play a game where they risk losing their beloved character
just like that.
- Many players say they like the _idea_ of permadeath except when it could happen to them.
- It can limit roleplaying freedom and make people refuse to take any risks.
- It may make players even more reluctant to play conflict-driving 'bad guys'.
- Game balance is much, much more important when results are "final". This escalates the severity of 'unfairness'
a hundred-fold. Things like bugs or exploits can also lead to much more server effects.
For these reasons, it's very common to do hybrid systems. Some tried variations:
- NPCs cannot kill you, only other players can.
- NPCs cannot kill you, only other players can.
- Death is permanent, but it's difficult to actually die - you are much more likely to end up being severely
hurt/incapacitated.
hurt/incapacitated.
- You can pre-pay 'insurance' to magically/technologically avoid actually dying. Only if don't have insurance will
you die permanently.
- Death just means harsh penalties, not actual death.
- When you die you can fight your way back to life from some sort of afterlife.
- When you die you can fight your way back to life from some sort of afterlife.
- You'll only die permanently if you as a player explicitly allows it.
For our tutorial-game we will not be messing with perma-death; instead your defeat will mean you will re-spawn
back at your home location with a fraction of your health.
back at your home location with a fraction of your health.
## Conclusions
## Conclusions
Going through the questions has helped us get a little bit more of a feel for the game we want to do. There are
Going through the questions has helped us get a little bit more of a feel for the game we want to do. There are
many other things we could ask ourselves, but if we can cover these points we will be a good way towards a complete,
playable game!
playable game!
Before starting to code in earnest a good coder should always do an inventory of all the stuff they _don't_ need
to code themselves. So in the next lesson we will check out what help we have from Evennia's _contribs_.
to code themselves. So in the next lesson we will check out what help we have from Evennia's _contribs_.

View file

@ -121,7 +121,7 @@ question. And maybe youll find that you have a better feeling for the answer
Once you are out of the starting tutorial, you'll be off to do your own thing.
- The starting tutorial cannot cover everything. Skim through the [Evennia docs](../../../index).
- The starting tutorial cannot cover everything. Skim through the [Evennia docs](../../../index.md).
Even if you don't read everything, it gives you a feeling for what's available should you need
to look for something later. Make sure to use the search function.
- You can now start by expanding on the tutorial-game we will have created. In the last part there

View file

@ -1,12 +1,13 @@
# Evennia Starting Tutorial (Part 2)
```sidebar:: Tutorial Parts
```{eval-rst}
.. sidebar:: Tutorial Parts
Part 1: `What we have <../Part1/Starting-Part1.html>`_
A tour of Evennia and how to use the tools, including an introduction to Python.
**Part 2: What we want**
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>`_
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
@ -16,25 +17,25 @@
## Lessons for Part 2
In Part two of the Starting tutorial we'll step back and plan out the kind of tutorial
In Part two of the Starting tutorial we'll step back and plan out the kind of tutorial
game we want to make. This is a more 'theoretical' part where we won't do any hands-on
programming.
programming.
1. Introduction & Overview (you are here)
1. [Where do I begin](./Planning-Where-Do-I-Begin)
1. [On planning a game](./Game-Planning)
1. [Planning to use some useful Contribs](./Planning-Some-Useful-Contribs)
1. [Where do I begin](./Planning-Where-Do-I-Begin.md)
1. [On planning a game](./Game-Planning.md)
1. [Planning to use some useful Contribs](./Planning-Some-Useful-Contribs.md)
In the process we'll go through the common questions of "where to start"
and "what to think about" when creating a multiplayer online text game.
```toctree::
:hidden:
```{toctree}
:hidden:
Planning-Where-Do-I-Begin
Game-Planning
Planning-Some-Useful-Contribs
../Part3/Starting-Part3
Planning-Where-Do-I-Begin
Game-Planning
Planning-Some-Useful-Contribs
../Part3/Starting-Part3
```

View file

@ -1,32 +1,32 @@
[prev lesson](../../../Unimplemented) | [next lesson](../../../Unimplemented)
[prev lesson](../../../Unimplemented.md) | [next lesson](../../../Unimplemented.md)
# Making a sittable object
# Making a sittable object
In this lesson we will go through how to make a chair you can sit on. Sounds easy, right?
Well it is. But in the process of making the chair we will need to consider the various ways
to do it depending on how we want our game to work.
In this lesson we will go through how to make a chair you can sit on. Sounds easy, right?
Well it is. But in the process of making the chair we will need to consider the various ways
to do it depending on how we want our game to work.
The goals of this lesson are as follows:
- We want a new 'sittable' object, a Chair in particular".
- We want to be able to use a command to sit in the chair.
- We want to be able to use a command to sit in the chair.
- Once we are sitting in the chair it should affect us somehow. To demonstrate this we'll
set a flag "Resting" on the Character sitting in the Chair.
set a flag "Resting" on the Character sitting in the Chair.
- When you sit down you should not be able to walk to another room without first standing up.
- A character should be able to stand up and move away from the chair.
- A character should be able to stand up and move away from the chair.
There are two main ways to design the commands for sitting and standing up.
- You can store the commands on the chair so they are only available when a chair is in the room
- You can store the commands on the Character so they are always available and you must always specify
which chair to sit on.
Both of these are very useful to know about, so in this lesson we'll try both. But first
we need to handle some basics.
we need to handle some basics.
## Don't move us when resting
When you are sitting in a chair you can't just walk off without first standing up.
When you are sitting in a chair you can't just walk off without first standing up.
This requires a change to our Character typeclass. Open `mygame/typeclasses/characters.py`:
```python
@ -39,7 +39,7 @@ class Character(DefaultCharacter):
def at_before_move(self, destination):
"""
Called by self.move_to when trying to move somewhere. If this returns
False, the move is immediately cancelled.
False, the move is immediately cancelled.
"""
if self.db.is_resting:
self.msg("You can't go anywhere while resting.")
@ -48,15 +48,15 @@ class Character(DefaultCharacter):
```
When moving somewhere, [character.move_to](api.objects.objects#Object.move_to) is called. This in turn
When moving somewhere, [character.move_to](evennia.objects.objects.DefaultObject.move_to) is called. This in turn
will call `character.at_before_move`. Here we look for an Attribute `is_resting` (which we will assign below)
to determine if we are stuck on the chair or not.
to determine if we are stuck on the chair or not.
## Making the Chair itself
## Making the Chair itself
Next we need the Chair itself, or rather a whole family of "things you can sit on" that we will call
_sittables_. We can't just use a default Object since we want a sittable to contain some custom code. We need
a new, custom Typeclass. Create a new module `mygame/typeclasses/sittables.py` with the following content:
Next we need the Chair itself, or rather a whole family of "things you can sit on" that we will call
_sittables_. We can't just use a default Object since we want a sittable to contain some custom code. We need
a new, custom Typeclass. Create a new module `mygame/typeclasses/sittables.py` with the following content:
```python
@ -69,8 +69,8 @@ class Sittable(DefaultObject):
def do_sit(self, sitter):
"""
Called when trying to sit on/in this object.
Called when trying to sit on/in this object.
Args:
sitter (Object): The one trying to sit down.
@ -78,43 +78,43 @@ class Sittable(DefaultObject):
current = self.db.sitter
if current:
if current == sitter:
sitter.msg("You are already sitting on {self.key}.")
sitter.msg("You are already sitting on {self.key}.")
else:
sitter.msg(f"You can't sit on {self.key} "
f"- {current.key} is already sitting there!")
return
return
self.db.sitting = sitter
sitter.db.is_resting = True
sitter.msg(f"You sit on {self.key}")
def do_stand(self, stander):
"""
Called when trying to stand from this object.
Called when trying to stand from this object.
Args:
stander (Object): The one trying to stand up.
"""
current = self.db.sitter
current = self.db.sitter
if not stander == current:
stander.msg(f"You are not sitting on {self.key}.")
else:
self.db.sitting = None
stander.db.is_resting = False
stander.db.is_resting = False
stander.msg(f"You stand up from {self.key}")
```
Here we have a small Typeclass that handles someone trying to sit on it. It has two methods that we can simply
Here we have a small Typeclass that handles someone trying to sit on it. It has two methods that we can simply
call from a Command later. We set the `is_resting` Attribute on the one sitting down.
One could imagine that one could have the future `sit` command check if someone is already sitting in the
One could imagine that one could have the future `sit` command check if someone is already sitting in the
chair instead. This would work too, but letting the `Sittable` class handle the logic around who can sit on it makes
logical sense.
logical sense.
We let the typeclass handle the logic, and also let it do all the return messaging. This makes it easy to churn out
a bunch of chairs for people to sit on. But it's not perfect. The `Sittable` class is general. What if you want to
make an armchair. You sit "in" an armchair rather than "on" it. We _could_ make a child class of `Sittable` named
`SittableIn` that makes this change, but that feels excessive. Instead we will make it so that Sittables can
`SittableIn` that makes this change, but that feels excessive. Instead we will make it so that Sittables can
modify this per-instance:
@ -126,13 +126,13 @@ class Sittable(DefaultObject):
def at_object_creation(self):
self.db.sitter = None
# do you sit "on" or "in" this object?
# do you sit "on" or "in" this object?
self.db.adjective = "on"
def do_sit(self, sitter):
"""
Called when trying to sit on/in this object.
Called when trying to sit on/in this object.
Args:
sitter (Object): The one trying to sit down.
@ -141,46 +141,46 @@ class Sittable(DefaultObject):
current = self.db.sitter
if current:
if current == sitter:
sitter.msg(f"You are already sitting {adjective} {self.key}.")
sitter.msg(f"You are already sitting {adjective} {self.key}.")
else:
sitter.msg(
f"You can't sit {adjective} {self.key} "
f"- {current.key} is already sitting there!")
return
return
self.db.sitting = sitter
sitter.db.is_resting = True
sitter.msg(f"You sit {adjective} {self.key}")
def do_stand(self, stander):
"""
Called when trying to stand from this object.
Called when trying to stand from this object.
Args:
stander (Object): The one trying to stand up.
"""
current = self.db.sitter
current = self.db.sitter
if not stander == current:
stander.msg(f"You are not sitting {self.db.adjective} {self.key}.")
else:
self.db.sitting = None
stander.db.is_resting = False
stander.db.is_resting = False
stander.msg(f"You stand up from {self.key}")
```
We added a new Attribute `adjective` which will probably usually be `in` or `on` but could also be `at` if you
want to be able to sit _at a desk_ for example. A regular builder would use it like this:
want to be able to sit _at a desk_ for example. A regular builder would use it like this:
> create/drop armchair : sittables.Sittable
> set armchair/adjective = in
This is probably enough. But all those strings are hard-coded. What if we want some more dramatic flair when you
sit down?
sit down?
You sit down and a whoopie cushion makes a loud fart noise!
You sit down and a whoopie cushion makes a loud fart noise!
For this we need to allow some further customization. Let's let the current strings be defaults that
we can replace.
For this we need to allow some further customization. Let's let the current strings be defaults that
we can replace.
```python
@ -188,13 +188,13 @@ from evennia import DefaultObject
class Sittable(DefaultObject):
"""
An object one can sit on
An object one can sit on
Customizable Attributes:
Customizable Attributes:
adjective: How to sit (on, in, at etc)
Return messages (set as Attributes):
msg_already_sitting: Already sitting here
format tokens {adjective} and {key}
format tokens {adjective} and {key}
msg_other_sitting: Someone else is sitting here.
format tokens {adjective}, {key} and {other}
msg_sitting_down: Successfully sit down
@ -207,13 +207,13 @@ class Sittable(DefaultObject):
"""
def at_object_creation(self):
self.db.sitter = None
# do you sit "on" or "in" this object?
# do you sit "on" or "in" this object?
self.db.adjective = "on"
def do_sit(self, sitter):
"""
Called when trying to sit on/in this object.
Called when trying to sit on/in this object.
Args:
sitter (Object): The one trying to sit down.
@ -235,7 +235,7 @@ class Sittable(DefaultObject):
else:
sitter.msg(f"You can't sit {adjective} {self.key} "
f"- {current.key} is already sitting there!")
return
return
self.db.sitting = sitter
sitter.db.is_resting = True
if self.db.msg_sitting_down:
@ -245,13 +245,13 @@ class Sittable(DefaultObject):
def do_stand(self, stander):
"""
Called when trying to stand from this object.
Called when trying to stand from this object.
Args:
stander (Object): The one trying to stand up.
"""
current = self.db.sitter
current = self.db.sitter
if not stander == current:
if self.db.msg_standing_fail:
stander.msg(self.db.msg_standing_fail.format(
@ -260,7 +260,7 @@ class Sittable(DefaultObject):
stander.msg(f"You are not sitting {self.db.adjective} {self.key}")
else:
self.db.sitting = None
stander.db.is_resting = False
stander.db.is_resting = False
if self.db.msg_standing_up:
stander.msg(self.db.msg_standing_up.format(
adjective=self.db.adjective, key=self.key))
@ -268,55 +268,55 @@ class Sittable(DefaultObject):
stander.msg(f"You stand up from {self.key}")
```
Here we really went all out with flexibility. If you need this much is up to you.
We added a bunch of optional Attributes to hold alternative versions of all the messages.
There are some things to note:
Here we really went all out with flexibility. If you need this much is up to you.
We added a bunch of optional Attributes to hold alternative versions of all the messages.
There are some things to note:
- We don't actually initiate those Attributes in `at_object_creation`. This is a simple
optimization. The assumption is that _most_ chairs will probably not be this customized.
So initiating a bunch of Attributes to, say, empty strings would be a lot of useless database calls.
- We don't actually initiate those Attributes in `at_object_creation`. This is a simple
optimization. The assumption is that _most_ chairs will probably not be this customized.
So initiating a bunch of Attributes to, say, empty strings would be a lot of useless database calls.
The drawback is that the available Attributes become less visible when reading the code. So we add a long
describing docstring to the end to explain all you can use.
describing docstring to the end to explain all you can use.
- We use `.format` to inject formatting-tokens in the text. The good thing about such formatting
markers is that they are _optional_. They are there if you want them, but Python will not complain
if you don't include some or any of them. Let's see an example:
markers is that they are _optional_. They are there if you want them, but Python will not complain
if you don't include some or any of them. Let's see an example:
> reload # if you have new code
> create/drop armchair : sittables.Sittable
> create/drop armchair : sittables.Sittable
> set armchair/adjective = in
> set armchair/msg_sitting_down = As you sit down {adjective} {key}, life feels easier.
> set armchair/msg_standing_up = You stand up from {key}. Life resumes.
The `{key}` and `{adjective}` are examples of optional formatting markers. Whenever the message is
returned, the format-tokens within will be replaced with `armchair` and `in` respectively. Should we
The `{key}` and `{adjective}` are examples of optional formatting markers. Whenever the message is
returned, the format-tokens within will be replaced with `armchair` and `in` respectively. Should we
rename the chair later, this will show in the messages automatically (since `{key}` will change).
We have no Command to use this chair yet. But we can try it out with `py`:
> py self.search("armchair").do_sit(self)
As you sit down in armchair, life feels easier.
> self.db.resting
As you sit down in armchair, life feels easier.
> self.db.resting
True
> py self.search("armchair").do_stand(self)
You stand up from armchair. Life resumes
> self.db.resting
False
If you follow along and get a result like this, all seems to be working well!
You stand up from armchair. Life resumes
> self.db.resting
False
If you follow along and get a result like this, all seems to be working well!
## Command variant 1: Commands on the chair
This way to implement `sit` and `stand` puts new cmdsets on the Sittable itself.
As we've learned before, commands on objects are made available to others in the room.
This makes the command easy but instead adds some complexity in the management of the CmdSet.
As we've learned before, commands on objects are made available to others in the room.
This makes the command easy but instead adds some complexity in the management of the CmdSet.
This is how it will look if `armchair` is in the room:
This is how it will look if `armchair` is in the room:
> sit
As you sit down in armchair, life feels easier.
> sit
As you sit down in armchair, life feels easier.
What happens if there are sittables `sofa` and `barstool` also in the room? Evennia will automatically
handle this for us and allow us to specify which one we want:
handle this for us and allow us to specify which one we want:
> sit
More than one match for 'sit' (please narrow target):
@ -325,24 +325,24 @@ handle this for us and allow us to specify which one we want:
sit-3 (barstool)
> sit-1
As you sit down in armchair, life feels easier.
To keep things separate we'll make a new module `mygame/commands/sittables.py`:
```sidebar:: Separate Commands and Typeclasses?
```{sidebar} Separate Commands and Typeclasses?
You can organize these things as you like. If you wanted you could put the sit-command + cmdset
together with the `Sittable` typeclass in `mygame/typeclasses/sittables.py`. That has the advantage of
keeping everything related to sitting in one place. But there is also some organizational merit to
keeping all Commands in one place as we do here.
together with the `Sittable` typeclass in `mygame/typeclasses/sittables.py`. That has the advantage of
keeping everything related to sitting in one place. But there is also some organizational merit to
keeping all Commands in one place as we do here.
```
```python
from evennia import Command, CmdSet
class CmdSit(Command):
class CmdSit(Command):
"""
Sit down.
Sit down.
"""
key = "sit"
@ -366,65 +366,65 @@ class CmdSetSit(CmdSet):
```
As seen, the commands are nearly trivial. `self.obj` is the object to which we added the cmdset with this
Command (so for example a chair). We just call the `do_sit/stand` on that object and the `Sittable` will
As seen, the commands are nearly trivial. `self.obj` is the object to which we added the cmdset with this
Command (so for example a chair). We just call the `do_sit/stand` on that object and the `Sittable` will
do the rest.
Why that `priority = 1` on `CmdSetSit`? This makes same-named Commands from this cmdset merge with a bit higher
priority than Commands from the Character-cmdset. Why this is a good idea will become clear shortly.
priority than Commands from the Character-cmdset. Why this is a good idea will become clear shortly.
We also need to make a change to our `Sittable` typeclass. Open `mygame/typeclasses/sittables.py`:
```python
from evennia import DefaultObject
from commands.sittables import CmdSetSit # <- new
from commands.sittables import CmdSetSit # <- new
class Sittable(DefaultObject):
class Sittable(DefaultObject):
"""
(docstring)
"""
"""
def at_object_creation(self):
self.db.sitter = None
# do you sit "on" or "in" this object?
# do you sit "on" or "in" this object?
self.db.adjective = "on"
self.cmdset.add_default(CmdSetSit) # <- new
```
```
Any _new_ Sittables will now have your `sit` Command. Your existing `armchair` will not,
since `at_object_creation` will not re-run for already existing objects. We can update it manually:
Any _new_ Sittables will now have your `sit` Command. Your existing `armchair` will not,
since `at_object_creation` will not re-run for already existing objects. We can update it manually:
> reload
> update armchair
> reload
> update armchair
We could also update all existing sittables (all on one line):
> py from typeclasses.sittables import Sittable ;
> py from typeclasses.sittables import Sittable ;
[sittable.at_object_creation() for sittable in Sittable.objects.all()]
> The above shows an example of a _list comprehension_. Think of it as an efficient way to construct a new list
all in one line. You can read more about list comprehensions
> The above shows an example of a _list comprehension_. Think of it as an efficient way to construct a new list
all in one line. You can read more about list comprehensions
[here in the Python docs](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions).
We should now be able to use `sit` while in the room with the armchair.
> sit
> sit
As you sit down in armchair, life feels easier.
> stand
> stand
You stand up from armchair.
One issue with placing the `sit` (or `stand`) Command "on" the chair is that it will not be available when in a
room without a Sittable object:
One issue with placing the `sit` (or `stand`) Command "on" the chair is that it will not be available when in a
room without a Sittable object:
> sit
> sit
Command 'sit' is not available. ...
This is practical but not so good-looking; it makes it harder for the user to know a `sit` action is at all
possible. Here is a trick for fixing this. Let's add _another_ Command to the bottom
This is practical but not so good-looking; it makes it harder for the user to know a `sit` action is at all
possible. Here is a trick for fixing this. Let's add _another_ Command to the bottom
of `mygame/commands/sittables.py`:
```python
# ...
# ...
class CmdNoSitStand(Command):
"""
@ -441,15 +441,15 @@ class CmdNoSitStand(Command):
```
Here we have a Command that is actually two - it will answer to both `sit` and `stand` since we
added `stand` to its `aliases`. In the command we look at `self.cmdname`, which is the string
_actually used_ to call this command. We use this to return different messages.
Here we have a Command that is actually two - it will answer to both `sit` and `stand` since we
added `stand` to its `aliases`. In the command we look at `self.cmdname`, which is the string
_actually used_ to call this command. We use this to return different messages.
We don't need a separate CmdSet for this, instead we will add this
We don't need a separate CmdSet for this, instead we will add this
to the default Character cmdset. Open `mygame/commands/default_cmdsets.py`:
```python
# ...
# ...
from commands import sittables
class CharacterCmdSet(CmdSet):
@ -462,36 +462,36 @@ class CharacterCmdSet(CmdSet):
```
To test we'll build a new location without any comfy armchairs and go there:
To test we'll build a new location without any comfy armchairs and go there:
> reload
> tunnel n = kitchen
north
> sit
> reload
> tunnel n = kitchen
north
> sit
You have nothing to sit on.
> south
sit
> south
sit
As you sit down in armchair, life feels easier.
We now have a fully functioning `sit` action that is contained with the chair itself. When no chair is around, a
default error message is shown.
How does this work? There are two cmdsets at play, both of which have a `sit` Command. As you may remember we
set the chair's cmdset to `priority = 1`. This is where that matters. The default Character cmdset has a
We now have a fully functioning `sit` action that is contained with the chair itself. When no chair is around, a
default error message is shown.
How does this work? There are two cmdsets at play, both of which have a `sit` Command. As you may remember we
set the chair's cmdset to `priority = 1`. This is where that matters. The default Character cmdset has a
priority of 0. This means that whenever we enter a room with a Sittable thing, the `sit` command
from _its_ cmdset will take _precedence_ over the Character cmdset's version. So we are actually picking
from _its_ cmdset will take _precedence_ over the Character cmdset's version. So we are actually picking
_different_ `sit` commands depending on circumstance! The user will never be the wiser.
So this handles `sit`. What about `stand`? That will work just fine:
So this handles `sit`. What about `stand`? That will work just fine:
> stand
You stand up from armchair.
You stand up from armchair.
> north
> stand
You are not sitting down.
We have one remaining problem with `stand` though - what happens when you are sitting down and try to
`stand` in a room with more than one chair:
> stand
You are not sitting down.
We have one remaining problem with `stand` though - what happens when you are sitting down and try to
`stand` in a room with more than one chair:
> stand
More than one match for 'stand' (please narrow target):
@ -499,12 +499,12 @@ We have one remaining problem with `stand` though - what happens when you are si
stand-2 (sofa)
stand-3 (barstool)
Since all the sittables have the `stand` Command on them, you'll get a multi-match error. This _works_ ... but
Since all the sittables have the `stand` Command on them, you'll get a multi-match error. This _works_ ... but
you could pick _any_ of those sittables to "stand up from". That's really weird and non-intuitive. With `sit` it
was okay to get a choice - Evennia can't know which chair we intended to sit on. But we know which chair we
sit on so we should only get _its_ `stand` command.
was okay to get a choice - Evennia can't know which chair we intended to sit on. But we know which chair we
sit on so we should only get _its_ `stand` command.
We will fix this with a `lock` and a custom `lock function`. We want a lock on the `stand` Command that only
We will fix this with a `lock` and a custom `lock function`. We want a lock on the `stand` Command that only
makes it available when the caller is actually sitting on the chair the `stand` command is on.
First let's add the lock so we see what we want. Open `mygame/commands/sittables.py`:
@ -518,37 +518,37 @@ class CmdStand(Command):
"""
key = "stand"
lock = "cmd:sitsonthis()" # < this is new
def func(self):
self.obj.do_stand(self.caller)
# ...
```
We define a [Lock](../../../Components/Locks) on the command. The `cmd:` is in what situation Evennia will check
the lock. The `cmd` means that it will check the lock when determining if a user has access to this command or not.
What will be checked is the `sitsonthis` _lock function_ which doesn't exist yet.
We define a [Lock](../../../Components/Locks.md) on the command. The `cmd:` is in what situation Evennia will check
the lock. The `cmd` means that it will check the lock when determining if a user has access to this command or not.
What will be checked is the `sitsonthis` _lock function_ which doesn't exist yet.
Open `mygame/server/conf/lockfuncs.py` to add it!
Open `mygame/server/conf/lockfuncs.py` to add it!
```python
"""
(module lockstring)
"""
# ...
# ...
def sitsonthis(accessing_obj, accessed_obj, *args, **kwargs):
"""
True if accessing_obj is sitting on/in the accessed_obj.
True if accessing_obj is sitting on/in the accessed_obj.
"""
return accessed_obj.db.sitting == accessing_obj
# ...
# ...
```
Evennia knows that all functions in `mygame/server/conf/lockfuncs` should be possible to use in a lock definition.
The arguments are required and Evennia will pass all relevant objects to them:
The arguments are required and Evennia will pass all relevant objects to them:
```sidebar:: Lockfuncs
```{sidebar} Lockfuncs
Evennia provides a large number of default lockfuncs, such as checking permission-levels,
if you are carrying or are inside the accessed object etc. There is no concept of 'sitting'
@ -556,53 +556,53 @@ The arguments are required and Evennia will pass all relevant objects to them:
```
- `accessing_obj` is the one trying to access the lock. So us, in this case.
- `accessing_obj` is the one trying to access the lock. So us, in this case.
- `accessed_obj` is the entity we are trying to gain a particular type of access to. So the chair.
- `args` is a tuple holding any arguments passed to the lockfunc. Since we use `sitsondthis()` this will
- `args` is a tuple holding any arguments passed to the lockfunc. Since we use `sitsondthis()` this will
be empty (and if we add anything, it will be ignored).
- `kwargs` is a tuple of keyword arguments passed to the lockfuncs. This will be empty as well in our example.
If you are superuser, it's important that you `quell` yourself before trying this out. This is because the superuser
If you are superuser, it's important that you `quell` yourself before trying this out. This is because the superuser
bypasses all locks - it can never get locked out, but it means it will also not see the effects of a lock like this.
> reload
> quell
> stand
You stand up from armchair
None of the other sittables' `stand` commands passed the lock and only the one we are actually sitting on did.
> reload
> quell
> stand
You stand up from armchair
None of the other sittables' `stand` commands passed the lock and only the one we are actually sitting on did.
Adding a Command to the chair object like this is powerful and a good technique to know. It does come with some
caveats though that one needs to keep in mind.
caveats though that one needs to keep in mind.
We'll now try another way to add the `sit/stand` commands.
## Command variant 2: Command on Character
## Command variant 2: Command on Character
Before we start with this, delete the chairs you've created (`del armchair` etc) and then do the following
changes:
Before we start with this, delete the chairs you've created (`del armchair` etc) and then do the following
changes:
- In `mygame/typeclasses/sittables.py`, comment out the line `self.cmdset.add_default(CmdSetSit)`.
- In `mygame/commands/default_cmdsets.py`, comment out the line `self.add(sittables.CmdNoSitStand)`.
- In `mygame/commands/default_cmdsets.py`, comment out the line `self.add(sittables.CmdNoSitStand)`.
This disables the on-object command solution so we can try an alternative. Make sure to `reload` so the
changes are known to Evennia.
This disables the on-object command solution so we can try an alternative. Make sure to `reload` so the
changes are known to Evennia.
In this variation we will put the `sit` and `stand` commands on the `Character` instead of on the chair. This
makes some things easier, but makes the Commands themselves more complex because they will not know which
chair to sit on. We can't just do `sit` anymore. This is how it will work.
makes some things easier, but makes the Commands themselves more complex because they will not know which
chair to sit on. We can't just do `sit` anymore. This is how it will work.
> sit <chair>
> sit <chair>
You sit on chair.
> stand
> stand
You stand up from chair.
Open `mygame/commands.sittables.py` again. We'll add a new sit-command. We name the class `CmdSit2` since
we already have `CmdSit` from the previous example. We put everything at the end of the module to
keep it separate.
Open `mygame/commands.sittables.py` again. We'll add a new sit-command. We name the class `CmdSit2` since
we already have `CmdSit` from the previous example. We put everything at the end of the module to
keep it separate.
```python
from evennia import Command, CmdSet
from evennia import Command, CmdSet
from evennia import InterruptCommand # <- this is new
class CmdSit(Command):
@ -610,18 +610,18 @@ class CmdSit(Command):
# ...
# new from here
# new from here
class CmdSit2(Command):
"""
Sit down.
Usage:
Usage:
sit <sittable>
"""
"""
key = "sit"
def parse(self):
self.args = self.args.strip()
if not self.args:
@ -641,7 +641,7 @@ class CmdSit2(Command):
```
With this Command-variation we need to search for the sittable. A series of methods on the Command
With this Command-variation we need to search for the sittable. A series of methods on the Command
are run in sequence:
1. `Command.at_pre_command` - this is not used by default
@ -649,77 +649,77 @@ are run in sequence:
3. `Command.func` - this should implement the actual Command functionality
4. `Command.at_post_func` - this is not used by default
So if we just `return` in `.parse`, `.func` will still run, which is not what we want. To immediately
So if we just `return` in `.parse`, `.func` will still run, which is not what we want. To immediately
abort this sequence we need to `raise InterruptCommand`.
```sidebar:: Raising exceptions
```{sidebar} Raising exceptions
Raising an exception allows for immediately interrupting the current program flow. Python
automatically raises error-exceptions when detecting problems with the code. It will be
Raising an exception allows for immediately interrupting the current program flow. Python
automatically raises error-exceptions when detecting problems with the code. It will be
raised up through the sequence of called code (the 'stack') until it's either `caught` with
a `try ... except` or reaches the outermost scope where it'll be logged or displayed.
```
`InterruptCommand` is an _exception_ that the Command-system catches with the understanding that we want
to do a clean abort. In the `.parse` method we strip any whitespaces from the argument and
sure there actuall _is_ an argument. We abort immediately if there isn't.
`InterruptCommand` is an _exception_ that the Command-system catches with the understanding that we want
to do a clean abort. In the `.parse` method we strip any whitespaces from the argument and
sure there actuall _is_ an argument. We abort immediately if there isn't.
We we get to `.func` at all, we know that we have an argument. We search for this and abort if we there was
a problem finding the target.
a problem finding the target.
> We could have done `raise InterruptCommand` in `.func` as well, but `return` is a little shorter to write
> and there is no harm done if `at_post_func` runs since it's empty.
Next we call the found sittable's `do_sit` method. Note that we wrap this call like this:
Next we call the found sittable's `do_sit` method. Note that we wrap this call like this:
```python
try:
# code
# code
except AttributeError:
# stuff to do if AttributeError exception was raised
```
The reason is that `caller.search` has no idea we are looking for a Sittable. The user could have tried
The reason is that `caller.search` has no idea we are looking for a Sittable. The user could have tried
`sit wall` or `sit sword`. These don't have a `do_sit` method _but we call it anyway and handle the error_.
This is a very "Pythonic" thing to do. The concept is often called "leap before you look" or "it's easier to
ask for forgiveness than for permission". If `sittable.do_sit` does not exist, Python will raise an `AttributeError`.
We catch this with `try ... except AttributeError` and convert it to a proper error message.
We catch this with `try ... except AttributeError` and convert it to a proper error message.
While it's useful to learn about `try ... except`, there is also a way to leverage Evennia to do this without
`try ... except`:
```python
# ...
# ...
def func(self):
# self.search handles all error messages etc.
sittable = self.caller.search(
self.args,
self.args,
typeclass="typeclasses.sittables.Sittable")
if not sittable:
return
sittable.do_sit(self.caller)
```
```sidebar:: Continuing across multiple lines
```{sidebar} Continuing across multiple lines
Note how the `.search()` method's arguments are spread out over multiple
lines. This works for all lists, tuples and other listings and is
lines. This works for all lists, tuples and other listings and is
a good way to avoid very long and hard-to-read lines.
```
The `caller.search` method has an keyword argument `typeclass` that can take either a python-path to a
The `caller.search` method has an keyword argument `typeclass` that can take either a python-path to a
typeclass, the typeclass itself, or a list of either to widen the allowed options. In this case we know
for sure that the `sittable` we get is actually a `Sittable` class and we can call `sittable.do_sit` without
needing to worry about catching errors.
needing to worry about catching errors.
Let's do the `stand` command while we are at it. Again, since the Command is external to the chair we don't
know which object we are sitting in and have to search for it.
know which object we are sitting in and have to search for it.
```python
@ -727,43 +727,43 @@ class CmdStand2(Command):
"""
Stand up.
Usage:
Usage:
stand
"""
"""
key = "stand"
def func(self):
caller = self.caller
caller = self.caller
# find the thing we are sitting on/in, by finding the object
# in the current location that as an Attribute "sitter" set
# in the current location that as an Attribute "sitter" set
# to the caller
sittable = caller.search(
caller,
caller,
candidates=caller.location.contents,
attribute_name="sitter",
attribute_name="sitter",
typeclass="typeclasses.sittables.Sittable")
# if this is None, the error was already reported to user
if not sittable:
return
return
sittable.do_stand(caller)
```
This forced us to to use the full power of the `caller.search` method. If we wanted to search for something
more complex we would likely need to break out a [Django query](../Part1/Django-queries) to do it. The key here is that
we know that the object we are looking for is a `Sittable` and that it must have an Attribute named `sitter`
which should be set to us, the one sitting on/in the thing. Once we have that we just call `.do_stand` on it
and let the Typeclass handle the rest.
This forced us to to use the full power of the `caller.search` method. If we wanted to search for something
more complex we would likely need to break out a [Django query](../Part1/Django-queries.md) to do it. The key here is that
we know that the object we are looking for is a `Sittable` and that it must have an Attribute named `sitter`
which should be set to us, the one sitting on/in the thing. Once we have that we just call `.do_stand` on it
and let the Typeclass handle the rest.
All that is left now is to make this available to us. This type of Command should be available to us all the time
so we can put it in the default Cmdset` on the Character. Open `mygame/default_cmdsets.py`
```python
# ...
# ...
from commands import sittables
class CharacterCmdSet(CmdSet):
@ -777,26 +777,26 @@ class CharacterCmdSet(CmdSet):
```
Now let's try it out:
Now let's try it out:
> reload
> reload
> create/drop sofa : sittables.Sittable
> sit sofa
You sit down on sofa.
> stand
> stand
You stand up from sofa.
## Conclusions
In this lesson we accomplished quite a bit:
In this lesson we accomplished quite a bit:
- We modified our `Character` class to avoid moving when sitting down.
- We made a new `Sittable` typeclass
- We tried two ways to allow a user to interact with sittables using `sit` and `stand` commands.
- We tried two ways to allow a user to interact with sittables using `sit` and `stand` commands.
Eagle-eyed readers will notice that the `stand` command sitting "on" the chair (variant 1) would work just fine
together with the `sit` command sitting "on" the Character (variant 2). There is nothing stopping you from
mixing them, or even try a third solution that better fits what you have in mind.
Eagle-eyed readers will notice that the `stand` command sitting "on" the chair (variant 1) would work just fine
together with the `sit` command sitting "on" the Character (variant 2). There is nothing stopping you from
mixing them, or even try a third solution that better fits what you have in mind.
[prev lesson](../../../Unimplemented) | [next lesson](../../../Unimplemented)
[prev lesson](../../../Unimplemented.md) | [next lesson](../../../Unimplemented.md)

View file

@ -45,12 +45,12 @@ makes it easier to change and update things in one place later.
values for Health, a list of skills etc, store those things on the Character - don't store how to
roll or change them.
- Next is to determine just how you want to store things on your Objects and Characters. You can
choose to either store things as individual [Attributes](../../../Components/Attributes), like `character.db.STR=34` and
choose to either store things as individual [Attributes](../../../Components/Attributes.md), like `character.db.STR=34` and
`character.db.Hunting_skill=20`. But you could also use some custom storage method, like a
dictionary `character.db.skills = {"Hunting":34, "Fishing":20, ...}`. A much more fancy solution is
to look at the Ainneve [Trait
handler](https://github.com/evennia/ainneve/blob/master/world/traits.py). Finally you could even go
with a [custom django model](../../../Concepts/New-Models). Which is the better depends on your game and the
with a [custom django model](../../../Concepts/New-Models.md). Which is the better depends on your game and the
complexity of your system.
- Make a clear [API](https://en.wikipedia.org/wiki/Application_programming_interface) into your
rules. That is, make methods/functions that you feed with, say, your Character and which skill you

View file

@ -1,6 +1,8 @@
# Evennia Starting Tutorial (Part 3)
```sidebar:: Tutorial Parts
```{eval-rst}
sidebar:: Tutorial Parts
Part 1: `What we have <../Part1/Starting-Part1.html>`_
A tour of Evennia and how to use the tools, including an introduction to Python.
@ -13,33 +15,33 @@
Part 5: `Showing the world <../Part5/Starting-Part5.html>`_
Taking our new game online and let players try it out
```
In part three of the Evennia Starting tutorial we will go through the creation of several key parts
of our tutorial game _EvAdventure_. As we go, we will test each part and create a simple "tech demo" to
show off all the moving parts.
In part three of the Evennia Starting tutorial we will go through the creation of several key parts
of our tutorial game _EvAdventure_. As we go, we will test each part and create a simple "tech demo" to
show off all the moving parts.
1. Introduction & Overview (you are here)
1. [Changing settings](../../../Unimplemented)
1. [Applying contribs](../../../Unimplemented)
1. [Creating a rule module](../../../Unimplemented)
1. [Tweaking the base Typeclasses](../../../Unimplemented)
1. [Character creation menu](../../../Unimplemented)
1. [Wearing armor and wielding weapons](../../../Unimplemented)
1. [Two types of combat](../../../Unimplemented)
1. [Monsters and AI](../../../Unimplemented)
1. [Questing and rewards](../../../Unimplemented)
1. [Overview of Tech demo](../../../Unimplemented)
1. [Changing settings](../../../Unimplemented.md)
1. [Applying contribs](../../../Unimplemented.md)
1. [Creating a rule module](../../../Unimplemented.md)
1. [Tweaking the base Typeclasses](../../../Unimplemented.md)
1. [Character creation menu](../../../Unimplemented.md)
1. [Wearing armor and wielding weapons](../../../Unimplemented.md)
1. [Two types of combat](../../../Unimplemented.md)
1. [Monsters and AI](../../../Unimplemented.md)
1. [Questing and rewards](../../../Unimplemented.md)
1. [Overview of Tech demo](../../../Unimplemented.md)
If you followed the previous parts of this tutorial you will have some notions about Python and where to find
and make use of things in Evennia. We also have a good idea of the type of game we want.
Even if this is not the game-style you are interested in, following along will give you a lot of experience
with using Evennia. This be of much use when doing your own thing later.
and make use of things in Evennia. We also have a good idea of the type of game we want.
Even if this is not the game-style you are interested in, following along will give you a lot of experience
with using Evennia. This be of much use when doing your own thing later.
_TODO_
```toctree::
:hidden:
```{toctree}
:hidden:
../../../Unimplemented
../../../Unimplemented
```
```

View file

@ -31,8 +31,8 @@ allows for emoting as part of combat which is an advantage for roleplay-heavy ga
To implement a freeform combat system all you need is a dice roller and a roleplaying rulebook. See
[contrib/dice.py](https://github.com/evennia/evennia/blob/master/evennia/contrib/dice.py) for an
example dice roller. To implement at twitch-based system you basically need a few combat
[commands](../../../Components/Commands), possibly ones with a [cooldown](../../Command-Cooldown). You also need a [game rule
module](Implementing-a-game-rule-system) that makes use of it. We will focus on the turn-based
[commands](../../../Components/Commands.md), possibly ones with a [cooldown](../../Command-Cooldown.md). You also need a [game rule
module](./Implementing-a-game-rule-system.md) that makes use of it. We will focus on the turn-based
variety here.
## Tutorial overview
@ -61,22 +61,22 @@ reported. A new turn then begins.
For creating the combat system we will need the following components:
- A combat handler. This is the main mechanic of the system. This is a [Script](../../../Components/Scripts) object
- A combat handler. This is the main mechanic of the system. This is a [Script](../../../Components/Scripts.md) object
created for each combat. It is not assigned to a specific object but is shared by the combating
characters and handles all the combat information. Since Scripts are database entities it also means
that the combat will not be affected by a server reload.
- A combat [command set](../../../Components/Command-Sets) with the relevant commands needed for combat, such as the
- A combat [command set](../../../Components/Command-Sets.md) with the relevant commands needed for combat, such as the
various attack/defend options and the `flee/disengage` command to leave the combat mode.
- A rule resolution system. The basics of making such a module is described in the [rule system
tutorial](Implementing-a-game-rule-system). We will only sketch such a module here for our end-turn
tutorial](./Implementing-a-game-rule-system.md). We will only sketch such a module here for our end-turn
combat resolution.
- An `attack` [command](../../../Components/Commands) for initiating the combat mode. This is added to the default
- An `attack` [command](../../../Components/Commands.md) for initiating the combat mode. This is added to the default
command set. It will create the combat handler and add the character(s) to it. It will also assign
the combat command set to the characters.
## The combat handler
The _combat handler_ is implemented as a stand-alone [Script](../../../Components/Scripts). This Script is created when
The _combat handler_ is implemented as a stand-alone [Script](../../../Components/Scripts.md). This Script is created when
the first Character decides to attack another and is deleted when no one is fighting any more. Each
handler represents one instance of combat and one combat only. Each instance of combat can hold any
number of characters but each character can only be part of one combat at a time (a player would
@ -89,7 +89,7 @@ don't use this very much here this might allow the combat commands on the charac
update the combat handler state directly.
_Note: Another way to implement a combat handler would be to use a normal Python object and handle
time-keeping with the [TickerHandler](../../../Components/TickerHandler). This would require either adding custom hook
time-keeping with the [TickerHandler](../../../Components/TickerHandler.md). This would require either adding custom hook
methods on the character or to implement a custom child of the TickerHandler class to track turns.
Whereas the TickerHandler is easy to use, a Script offers more power in this case._
@ -507,7 +507,7 @@ class CmdAttack(Command):
```
The `attack` command will not go into the combat cmdset but rather into the default cmdset. See e.g.
the [Adding Command Tutorial](../Part1/Adding-Commands) if you are unsure about how to do this.
the [Adding Command Tutorial](../Part1/Adding-Commands.md) if you are unsure about how to do this.
## Expanding the example

View file

@ -7,7 +7,7 @@ focused on free form storytelling. Even if you are not interested in MUSH:es, th
first game-type to try since it's not so code heavy. You will be able to use the same principles for
building other types of games.
The tutorial starts from scratch. If you did the [First Steps Coding](../Part1/Starting-Part1) tutorial
The tutorial starts from scratch. If you did the [First Steps Coding](../Part1/Starting-Part1.md) tutorial
already you should have some ideas about how to do some of the steps already.
The following are the (very simplistic and cut-down) features we will implement (this was taken from
@ -61,7 +61,7 @@ class Character(DefaultCharacter):
self.db.combat_score = 1
```
We defined two new [Attributes](../../../Components/Attributes) `power` and `combat_score` and set them to default
We defined two new [Attributes](../../../Components/Attributes.md) `power` and `combat_score` and set them to default
values. Make sure to `@reload` the server if you had it already running (you need to reload every
time you update your python code, don't worry, no accounts will be disconnected by the reload).
@ -94,8 +94,8 @@ check it. Using this method however will make it easy to add more functionality
What we need are the following:
- One character generation [Command](../../../Components/Commands) to set the "Power" on the `Character`.
- A chargen [CmdSet](../../../Components/Command-Sets) to hold this command. Lets call it `ChargenCmdset`.
- One character generation [Command](../../../Components/Commands.md) to set the "Power" on the `Character`.
- A chargen [CmdSet](../../../Components/Command-Sets.md) to hold this command. Lets call it `ChargenCmdset`.
- A custom `ChargenRoom` type that makes this set of commands available to players in such rooms.
- One such room to test things in.
@ -104,7 +104,7 @@ What we need are the following:
For this tutorial we will add all our new commands to `mygame/commands/command.py` but you could
split your commands into multiple module if you prefered.
For this tutorial character generation will only consist of one [Command](../../../Components/Commands) to set the
For this tutorial character generation will only consist of one [Command](../../../Components/Commands.md) to set the
Character s "power" stat. It will be called on the following MUSH-like form:
+setpower 4
@ -156,7 +156,7 @@ This is a pretty straightforward command. We do some error checking, then set th
We use a `help_category` of "mush" for all our commands, just so they are easy to find and separate
in the help list.
Save the file. We will now add it to a new [CmdSet](../../../Components/Command-Sets) so it can be accessed (in a full
Save the file. We will now add it to a new [CmdSet](../../../Components/Command-Sets.md) so it can be accessed (in a full
chargen system you would of course have more than one command here).
Open `mygame/commands/default_cmdsets.py` and import your `command.py` module at the top. We also
@ -210,7 +210,7 @@ class ChargenRoom(Room):
```
Note how new rooms created with this typeclass will always start with `ChargenCmdset` on themselves.
Don't forget the `persistent=True` keyword or you will lose the cmdset after a server reload. For
more information about [Command Sets](../../../Components/Command-Sets) and [Commands](../../../Components/Commands), see the respective
more information about [Command Sets](../../../Components/Command-Sets.md) and [Commands](../../../Components/Commands.md), see the respective
links.
### Testing chargen
@ -242,7 +242,7 @@ between fixes. Don't continue until the creation seems to have worked okay.
This should bring you to the chargen room. Being in there you should now have the `+setpower`
command available, so test it out. When you leave (via the `finish` exit), the command will go away
and trying `+setpower` should now give you a command-not-found error. Use `ex me` (as a privileged
user) to check so the `Power` [Attribute](../../../Components/Attributes) has been set correctly.
user) to check so the `Power` [Attribute](../../../Components/Attributes.md) has been set correctly.
If things are not working, make sure your typeclasses and commands are free of bugs and that you
have entered the paths to the various command sets and commands correctly. Check the logs or command
@ -397,7 +397,7 @@ There are a few ways to define the NPC class. We could in theory create a custom
and put a custom NPC-specific cmdset on all NPCs. This cmdset could hold all manipulation commands.
Since we expect NPC manipulation to be a common occurrence among the user base however, we will
instead put all relevant NPC commands in the default command set and limit eventual access with
[Permissions and Locks](../../../Components/Locks#Permissions).
[Permissions and Locks](../../../Components/Permissions.md).
### Creating an NPC with +createNPC
@ -454,13 +454,13 @@ class CmdCreateNPC(Command):
), exclude=caller)
```
Here we define a `+createnpc` (`+createNPC` works too) that is callable by everyone *not* having the
`nonpcs` "[permission](../../../Components/Locks#Permissions)" (in Evennia, a "permission" can just as well be used to
`nonpcs` "[permission](../../../Components/Permissions.md)" (in Evennia, a "permission" can just as well be used to
block access, it depends on the lock we define). We create the NPC object in the caller's current
location, using our custom `Character` typeclass to do so.
We set an extra lock condition on the NPC, which we will use to check who may edit the NPC later --
we allow the creator to do so, and anyone with the Builders permission (or higher). See
[Locks](../../../Components/Locks) for more information about the lock system.
[Locks](../../../Components/Locks.md) for more information about the lock system.
Note that we just give the object default permissions (by not specifying the `permissions` keyword
to the `create_object()` call). In some games one might want to give the NPC the same permissions
@ -475,7 +475,7 @@ Since we re-used our custom character typeclass, our new NPC already has a *Powe
defaults to 1. How do we change this?
There are a few ways we can do this. The easiest is to remember that the `power` attribute is just a
simple [Attribute](../../../Components/Attributes) stored on the NPC object. So as a Builder or Admin we could set this
simple [Attribute](../../../Components/Attributes.md) stored on the NPC object. So as a Builder or Admin we could set this
right away with the default `@set` command:
@set mynpc/power = 6
@ -660,6 +660,6 @@ The simple "Power" game mechanic should be easily expandable to something more f
useful, same is true for the combat score principle. The `+attack` could be made to target a
specific player (or npc) and automatically compare their relevant attributes to determine a result.
To continue from here, you can take a look at the [Tutorial World](../Part1/Tutorial-World-Introduction). For
more specific ideas, see the [other tutorials and hints](../../Howto-Overview) as well
as the [Evennia Component overview](../../../Components/Components-Overview).
To continue from here, you can take a look at the [Tutorial World](../Part1/Tutorial-World-Introduction.md). For
more specific ideas, see the [other tutorials and hints](../../Howto-Overview.md) as well
as the [Evennia Component overview](../../../Components/Components-Overview.md).

View file

@ -1,23 +1,24 @@
# Evennia Starting Tutorial (Part 4)
```sidebar:: Tutorial Parts
```{eval-rst}
sidebar:: Tutorial Parts
Part 1: `What we have <../Part1/Starting-Part1.html>`_
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>`_
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
Getting down to the meat of extending Evennia to make our game to make a tech-demo
**Part 4: Using what we created**
Using the 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
```
```
We now have the code underpinnings of everything we need. We have also tested the various components
We now have the code underpinnings of everything we need. We have also tested the various components
and has a simple tech-demo to show it all works together. But there is no real coherence to it at this
point - we need to actually make a world.
point - we need to actually make a world.
In part four we will expand our tech demo into a more full-fledged (if small) game by use of batchcommand
and batchcode processors.
and batchcode processors.
_TODO_
_TODO_

View file

@ -4,14 +4,14 @@
Evennia leverages [Django](https://docs.djangoproject.com) which is a web development framework.
Huge professional websites are made in Django and there is extensive documentation (and books) on it
. You are encouraged to at least look at the Django basic tutorials. Here we will just give a brief
introduction for how things hang together, to get you started.
introduction for how things hang together, to get you started.
We assume you have installed and set up Evennia to run. A webserver and website comes out of the
box. You can get to that by entering `http://localhost:4001` in your web browser - you should see a
welcome page with some game statistics and a link to the web client. Let us add a new page that you
can get to by going to `http://localhost:4001/story`.
### Create the view
## Create the view
A django "view" is a normal Python function that django calls to render the HTML page you will see
in the web browser. Here we will just have it spit back the raw html, but Django can do all sorts of
@ -32,12 +32,12 @@ def storypage(request):
This view takes advantage of a shortcut provided to use by Django, _render_. This shortcut gives the
template some information from the request, for instance, the game name, and then renders it.
### The HTML page
## The HTML page
We need to find a place where Evennia (and Django) looks for html files (called *templates* in
Django parlance). You can specify such places in your settings (see the `TEMPLATES` variable in
`default_settings.py` for more info), but here we'll use an existing one. Go to
`mygame/template/overrides/website/` and create a page `story.html` there.
`mygame/template/overrides/website/` and create a page `story.html` there.
This is not a HTML tutorial, so we'll go simple:
@ -69,14 +69,14 @@ If you'd rather not take advantage of Evennia's base styles, you can do somethin
</html>
```
### The URL
## The URL
When you enter the address `http://localhost:4001/story` in your web browser, Django will parse that
field to figure out which page you want to go to. You tell it which patterns are relevant in the
file
[mygame/web/urls.py](https://github.com/evennia/evennia/blob/master/evennia/game_template/web/urls.py).
Open it now.
Open it now.
Django looks for the variable `urlpatterns` in this file. You want to add your new pattern to the
`custom_patterns` list we have prepared - that is then merged with the default `urlpatterns`. Here's
@ -97,4 +97,4 @@ instance. The first argument to `url` is the pattern of the url we want to find
a regular expression if you are familiar with those) and then our view function we want to direct
to.
That should be it. Reload Evennia and you should be able to browse to your new story page!
That should be it. Reload Evennia and you should be able to browse to your new story page!

View file

@ -1,12 +1,13 @@
# Evennia Starting Tutorial (part 5)
```sidebar:: Tutorial Parts
```{eval-rst}
.. sidebar:: Tutorial Parts
Part 1: `What we have <../Part1/Starting-Part1.html>`_
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>`_
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
@ -15,7 +16,7 @@
```
You have a working game! In part five we will look at the web-components of Evennia and how to modify them
to fit your game. We will also look at hosting your game and if you feel up to it we'll also go through how
to bring your game online so you can invite your first players.
to fit your game. We will also look at hosting your game and if you feel up to it we'll also go through how
to bring your game online so you can invite your first players.
_TODO_
_TODO_

View file

@ -5,7 +5,7 @@ Evennia uses the [Django](https://www.djangoproject.com/) web framework as the b
database configuration and the website it provides. While a full understanding of Django requires
reading the Django documentation, we have provided this tutorial to get you running with the basics
and how they pertain to Evennia. This text details getting everything set up. The
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial) gives a more explicit example of making a
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial.md) gives a more explicit example of making a
custom web page connected to your game, and you may want to read that after finishing this guide.
## A Basic Overview
@ -25,7 +25,7 @@ like [CSS](https://en.wikipedia.org/wiki/CSS), [Javascript](https://en.wikipedia
and Image files (You may note your mygame/web folder does not have a `static` or `template` folder.
This is intended and explained further below). Django applications may also have a `models.py` file
for storing information in the database. We will not change any models here, take a look at the
[New Models](../../../Concepts/New-Models) page (as well as the [Django docs](https://docs.djangoproject.com/en/1.7/topics/db/models/) on models) if you are interested.
[New Models](../../../Concepts/New-Models.md) page (as well as the [Django docs](https://docs.djangoproject.com/en/1.7/topics/db/models/) on models) if you are interested.
There is also a root `urls.py` that determines the URL structure for the entire project. A starter
`urls.py` is included in the default game template, and automatically imports all of Evennia's
@ -104,7 +104,7 @@ run any extra commands to see these changes - reloading the web page in your bro
To replace the index page's text, we'll need to find the template for it. We'll go into more detail
about how to determine which template is used for rendering a page in the
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial). For now, you should know that the template we want to change
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial.md). For now, you should know that the template we want to change
is stored in `evennia/web/website/templates/website/index.html`.
To replace this template file, you will put your changed template inside the
@ -120,7 +120,7 @@ original file already has all the markup and tags, ready for editing.
## Further reading
For further hints on working with the web presence, you could now continue to the
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial) where you learn to make a web page that
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial.md) where you learn to make a web page that
displays in-game character stats. You can also look at [Django's own
tutorial](https://docs.djangoproject.com/en/1.7/intro/tutorial01/) to get more insight in how Django
works and what possibilities exist.