Doc refactor/renaming

This commit is contained in:
Griatch 2020-07-11 10:41:33 +02:00
parent 9d8e8d7693
commit b5b265ec3b
115 changed files with 518 additions and 434 deletions

View file

@ -1,349 +0,0 @@
# Coordinates
# Adding room coordinates in your game
This tutorial is moderately difficult in content. You might want to be familiar and at ease with
some Python concepts (like properties) and possibly Django concepts (like queries), although this
tutorial will try to walk you through the process and give enough explanations each time. If you
don't feel very confident with math, don't hesitate to pause, go to the example section, which shows
a tiny map, and try to walk around the code or read the explanation.
Evennia doesn't have a coordinate system by default. Rooms and other objects are linked by location
and content:
- An object can be in a location, that is, another object. Like an exit in a room.
- An object can access its content. A room can see what objects uses it as location (that would
include exits, rooms, characters and so on).
This system allows for a lot of flexibility and, fortunately, can be extended by other systems.
Here, I offer you a way to add coordinates to every room in a way most compliant with Evennia
design. This will also show you how to use coordinates, find rooms around a given point for
instance.
## Coordinates as tags
The first concept might be the most surprising at first glance: we will create coordinates as
[tags](../../Component/Tags).
> Why not attributes, wouldn't that be easier?
It would. We could just do something like `room.db.x = 3`. The advantage of using tags is that it
will be easy and effective to search. Although this might not seem like a huge advantage right now,
with a database of thousands of rooms, it might make a difference, particularly if you have a lot of
things based on coordinates.
Rather than giving you a step-by-step process, I'll show you the code. Notice that we use
properties to easily access and update coordinates. This is a Pythonic approach. Here's our first
`Room` class, that you can modify in `typeclasses/rooms.py`:
```python
# in typeclasses/rooms.py
from evennia import DefaultRoom
class Room(DefaultRoom):
"""
Rooms are like any Object, except their location is None
(which is default). They also use basetype_setup() to
add locks so they cannot be puppeted or picked up.
(to change that, use at_object_creation instead)
See examples/object.py for a list of
properties and methods available on all Objects.
"""
@property
def x(self):
"""Return the X coordinate or None."""
x = self.tags.get(category="coordx")
return int(x) if isinstance(x, str) else None
@x.setter
def x(self, x):
"""Change the X coordinate."""
old = self.tags.get(category="coordx")
if old is not None:
self.tags.remove(old, category="coordx")
if x is not None:
self.tags.add(str(x), category="coordx")
@property
def y(self):
"""Return the Y coordinate or None."""
y = self.tags.get(category="coordy")
return int(y) if isinstance(y, str) else None
@y.setter
def y(self, y):
"""Change the Y coordinate."""
old = self.tags.get(category="coordy")
if old is not None:
self.tags.remove(old, category="coordy")
if y is not None:
self.tags.add(str(y), category="coordy")
@property
def z(self):
"""Return the Z coordinate or None."""
z = self.tags.get(category="coordz")
return int(z) if isinstance(z, str) else None
@z.setter
def z(self, z):
"""Change the Z coordinate."""
old = self.tags.get(category="coordz")
if old is not None:
self.tags.remove(old, category="coordz")
if z is not None:
self.tags.add(str(z), category="coordz")
```
If you aren't familiar with the concept of properties in Python, I encourage you to read a good
tutorial on the subject. [This article on Python properties](https://www.programiz.com/python-
programming/property)
is well-explained and should help you understand the idea.
Let's look at our properties for `x`. First of all is the read property.
```python
@property
def x(self):
"""Return the X coordinate or None."""
x = self.tags.get(category="coordx")
return int(x) if isinstance(x, str) else None
```
What it does is pretty simple:
1. It gets the tag of category `"coordx"`. It's the tag category where we store our X coordinate.
The `tags.get` method will return `None` if the tag can't be found.
2. We convert the value to an integer, if it's a `str`. Remember that tags can only contain `str`,
so we'll need to convert it.
> I thought tags couldn't contain values?
Well, technically, they can't: they're either here or not. But using tag categories, as we have
done, we get a tag, knowing only its category. That's the basic approach to coordinates in this
tutorial.
Now, let's look at the method that will be called when we wish to set `x` in our room:
```python
@x.setter
def x(self, x):
"""Change the X coordinate."""
old = self.tags.get(category="coordx")
if old is not None:
self.tags.remove(old, category="coordx")
if x is not None:
self.tags.add(str(x), category="coordx")
```
1. First, we remove the old X coordinate, if it exists. Otherwise, we'd end up with two tags in our
room with "coordx" as their category, which wouldn't do at all.
2. Then we add the new tag, giving it the proper category.
> Now what?
If you add this code and reload your game, once you're logged in with a character in a room as its
location, you can play around:
```
@py here.x
@py here.x = 0
@py here.y = 3
@py here.z = -2
@py here.z = None
```
The code might not be that easy to read, but you have to admit it's fairly easy to use.
## Some additional searches
Having coordinates is useful for several reasons:
1. It can help in shaping a truly logical world, in its geography, at least.
2. It can allow to look for specific rooms at given coordinates.
3. It can be good in order to quickly find the rooms around a location.
4. It can even be great in path-finding (finding the shortest path between two rooms).
So far, our coordinate system can help with 1., but not much else. Here are some methods that we
could add to the `Room` typeclass. These methods will just be search methods. Notice that they are
class methods, since we want to get rooms.
### Finding one room
First, a simple one: how to find a room at a given coordinate? Say, what is the room at X=0, Y=0,
Z=0?
```python
class Room(DefaultRoom):
# ...
@classmethod
def get_room_at(cls, x, y, z):
"""
Return the room at the given location or None if not found.
Args:
x (int): the X coord.
y (int): the Y coord.
z (int): the Z coord.
Return:
The room at this location (Room) or None if not found.
"""
rooms = cls.objects.filter(
db_tags__db_key=str(x), db_tags__db_category="coordx").filter(
db_tags__db_key=str(y), db_tags__db_category="coordy").filter(
db_tags__db_key=str(z), db_tags__db_category="coordz")
if rooms:
return rooms[0]
return None
```
This solution includes a bit of [Django
queries](https://docs.djangoproject.com/en/1.11/topics/db/queries/).
Basically, what we do is reach for the object manager and search for objects with the matching tags.
Again, don't spend too much time worrying about the mechanism, the method is quite easy to use:
```
Room.get_room_at(5, 2, -3)
```
Notice that this is a class method: you will call it from `Room` (the class), not an instance.
Though you still can:
@py here.get_room_at(3, 8, 0)
### Finding several rooms
Here's another useful method that allows us to look for rooms around a given coordinate. This is
more advanced search and doing some calculation, beware! Look at the following section if you're
lost.
```python
from math import sqrt
class Room(DefaultRoom):
# ...
@classmethod
def get_rooms_around(cls, x, y, z, distance):
"""
Return the list of rooms around the given coordinates.
This method returns a list of tuples (distance, room) that
can easily be browsed. This list is sorted by distance (the
closest room to the specified position is always at the top
of the list).
Args:
x (int): the X coord.
y (int): the Y coord.
z (int): the Z coord.
distance (int): the maximum distance to the specified position.
Returns:
A list of tuples containing the distance to the specified
position and the room at this distance. Several rooms
can be at equal distance from the position.
"""
# Performs a quick search to only get rooms in a square
x_r = list(reversed([str(x - i) for i in range(0, distance + 1)]))
x_r += [str(x + i) for i in range(1, distance + 1)]
y_r = list(reversed([str(y - i) for i in range(0, distance + 1)]))
y_r += [str(y + i) for i in range(1, distance + 1)]
z_r = list(reversed([str(z - i) for i in range(0, distance + 1)]))
z_r += [str(z + i) for i in range(1, distance + 1)]
wide = cls.objects.filter(
db_tags__db_key__in=x_r, db_tags__db_category="coordx").filter(
db_tags__db_key__in=y_r, db_tags__db_category="coordy").filter(
db_tags__db_key__in=z_r, db_tags__db_category="coordz")
# We now need to filter down this list to find out whether
# these rooms are really close enough, and at what distance
# In short: we change the square to a circle.
rooms = []
for room in wide:
x2 = int(room.tags.get(category="coordx"))
y2 = int(room.tags.get(category="coordy"))
z2 = int(room.tags.get(category="coordz"))
distance_to_room = sqrt(
(x2 - x) ** 2 + (y2 - y) ** 2 + (z2 - z) ** 2)
if distance_to_room <= distance:
rooms.append((distance_to_room, room))
# Finally sort the rooms by distance
rooms.sort(key=lambda tup: tup[0])
return rooms
```
This gets more serious.
1. We have specified coordinates as parameters. We determine a broad range using the distance.
That is, for each coordinate, we create a list of possible matches. See the example below.
2. We then search for the rooms within this broader range. It gives us a square
around our location. Some rooms are definitely outside the range. Again, see the example below
to follow the logic.
3. We filter down the list and sort it by distance from the specified coordinates.
Notice that we only search starting at step 2. Thus, the Django search doesn't look and cache all
objects, just a wider range than what would be really necessary. This method returns a circle of
coordinates around a specified point. Django looks for a square. What wouldn't fit in the circle
is removed at step 3, which is the only part that includes systematic calculation. This method is
optimized to be quick and efficient.
### An example
An example might help. Consider this very simple map (a textual description follows):
```
4 A B C D
3 E F G H
2 I J K L
1 M N O P
1 2 3 4
```
The X coordinates are given below. The Y coordinates are given on the left. This is a simple
square with 16 rooms: 4 on each line, 4 lines of them. All the rooms are identified by letters in
this example: the first line at the top has rooms A to D, the second E to H, the third I to L and
the fourth M to P. The bottom-left room, X=1 and Y=1, is M. The upper-right room X=4 and Y=4 is D.
So let's say we want to find all the neighbors, distance 1, from the room J. J is at X=2, Y=2.
So we use:
Room.get_rooms_around(x=2, y=2, z=0, distance=1)
# we'll assume a z coordinate of 0 for simplicity
1. First, this method gets all the rooms in a square around J. So it gets E F G, I J K, M N O. If
you want, draw the square around these coordinates to see what's happening.
2. Next, we browse over this list and check the real distance between J (X=2, Y=2) and the room.
The four corners of the square are not in this circle. For instance, the distance between J and M
is not 1. If you draw a circle of center J and radius 1, you'll notice that the four corners of our
square (E, G, M and O) are not in this circle. So we remove them.
3. We sort by distance from J.
So in the end we might obtain something like this:
```
[
(0, J), # yes, J is part of this circle after all, with a distance of 0
(1, F),
(1, I),
(1, K),
(1, N),
]
```
You can try with more examples if you want to see this in action.
### To conclude
You can definitely use this system to map other objects, not just rooms. You can easily remove the
`Z coordinate too, if you simply need X and Y.

View file

@ -1,748 +0,0 @@
# Parsing command arguments, theory and best practices
This tutorial will elaborate on the many ways one can parse command arguments. The first step after
[adding a command](Part1/Adding-Commands) usually is to parse its arguments. There are lots of
ways to do it, but some are indeed better than others and this tutorial will try to present them.
If you're a Python beginner, this tutorial might help you a lot. If you're already familiar with
Python syntax, this tutorial might still contain useful information. There are still a lot of
things I find in the standard library that come as a surprise, though they were there all along.
This might be true for others.
In this tutorial we will:
- Parse arguments with numbers.
- Parse arguments with delimiters.
- Take a look at optional arguments.
- Parse argument containing object names.
## What are command arguments?
I'm going to talk about command arguments and parsing a lot in this tutorial. So let's be sure we
talk about the same thing before going any further:
> A command is an Evennia object that handles specific user input.
For instance, the default `look` is a command. After having created your Evennia game, and
connected to it, you should be able to type `look` to see what's around. In this context, `look` is
a command.
> Command arguments are additional text passed after the command.
Following the same example, you can type `look self` to look at yourself. In this context, `self`
is the text specified after `look`. `" self"` is the argument to the `look` command.
Part of our task as a game developer is to connect user inputs (mostly commands) with actions in the
game. And most of the time, entering commands is not enough, we have to rely on arguments for
specifying actions with more accuracy.
Take the `say` command. If you couldn't specify what to say as a command argument (`say hello!`),
you would have trouble communicating with others in the game. One would need to create a different
command for every kind of word or sentence, which is, of course, not practical.
Last thing: what is parsing?
> In our case, parsing is the process by which we convert command arguments into something we can
work with.
We don't usually use the command argument as is (which is just text, of type `str` in Python). We
need to extract useful information. We might want to ask the user for a number, or the name of
another character present in the same room. We're going to see how to do all that now.
## Working with strings
In object terms, when you write a command in Evennia (when you write the Python class), the
arguments are stored in the `args` attribute. Which is to say, inside your `func` method, you can
access the command arguments in `self.args`.
### self.args
To begin with, look at this example:
```python
class CmdTest(Command):
"""
Test command.
Syntax:
test [argument]
Enter any argument after test.
"""
key = "test"
def func(self):
self.msg(f"You have entered: {self.args}.")
```
If you add this command and test it, you will receive exactly what you have entered without any
parsing:
```
> test Whatever
You have entered: Whatever.
> test
You have entered: .
```
> The lines starting with `>` indicate what you enter into your client. The other lines are what
you receive from the game server.
Notice two things here:
1. The left space between our command key ("test", here) and our command argument is not removed.
That's why there are two spaces in our output at line 2. Try entering something like "testok".
2. Even if you don't enter command arguments, the command will still be called with an empty string
in `self.args`.
Perhaps a slight modification to our code would be appropriate to see what's happening. We will
force Python to display the command arguments as a debug string using a little shortcut.
```python
class CmdTest(Command):
"""
Test command.
Syntax:
test [argument]
Enter any argument after test.
"""
key = "test"
def func(self):
self.msg(f"You have entered: {self.args!r}.")
```
The only line we have changed is the last one, and we have added `!r` between our braces to tell
Python to print the debug version of the argument (the repr-ed version). Let's see the result:
```
> test Whatever
You have entered: ' Whatever'.
> test
You have entered: ''.
> test And something with '?
You have entered: " And something with '?".
```
This displays the string in a way you could see in the Python interpreter. It might be easier to
read... to debug, anyway.
I insist so much on that point because it's crucial: the command argument is just a string (of type
`str`) and we will use this to parse it. What you will see is mostly not Evennia-specific, it's
Python-specific and could be used in any other project where you have the same need.
### Stripping
As you've seen, our command arguments are stored with the space. And the space between the command
and the arguments is often of no importance.
> Why is it ever there?
Evennia will try its best to find a matching command. If the user enters your command key with
arguments (but omits the space), Evennia will still be able to find and call the command. You might
have seen what happened if the user entered `testok`. In this case, `testok` could very well be a
command (Evennia checks for that) but seeing none, and because there's a `test` command, Evennia
calls it with the arguments `"ok"`.
But most of the time, we don't really care about this left space, so you will often see code to
remove it. There are different ways to do it in Python, but a command use case is the `strip`
method on `str` and its cousins, `lstrip` and `rstrip`.
- `strip`: removes one or more characters (either spaces or other characters) from both ends of the
string.
- `lstrip`: same thing but only removes from the left end (left strip) of the string.
- `rstrip`: same thing but only removes from the right end (right strip) of the string.
Some Python examples might help:
```python
>>> ' this is '.strip() # remove spaces by default
'this is'
>>> " What if I'm right? ".lstrip() # strip spaces from the left
"What if I'm right? "
>>> 'Looks good to me...'.strip('.') # removes '.'
'Looks good to me'
>>> '"Now, what is it?"'.strip('"?') # removes '"' and '?' from both ends
'Now, what is it'
```
Usually, since we don't need the space separator, but still want our command to work if there's no
separator, we call `lstrip` on the command arguments:
```python
class CmdTest(Command):
"""
Test command.
Syntax:
test [argument]
Enter any argument after test.
"""
key = "test"
def parse(self):
"""Parse arguments, just strip them."""
self.args = self.args.lstrip()
def func(self):
self.msg(f"You have entered: {self.args!r}.")
```
> We are now beginning to override the command's `parse` method, which is typically useful just for
argument parsing. This method is executed before `func` and so `self.args` in `func()` will contain
our `self.args.lstrip()`.
Let's try it:
```
> test Whatever
You have entered: 'Whatever'.
> test
You have entered: ''.
> test And something with '?
You have entered: "And something with '?".
> test And something with lots of spaces
You have entered: 'And something with lots of spaces'.
```
Spaces at the end of the string are kept, but all spaces at the beginning are removed:
> `strip`, `lstrip` and `rstrip` without arguments will strip spaces, line breaks and other common
separators. You can specify one or more characters as a parameter. If you specify more than one
character, all of them will be stripped from your original string.
### Convert arguments to numbers
As pointed out, `self.args` is a string (of type `str`). What if we want the user to enter a
number?
Let's take a very simple example: creating a command, `roll`, that allows to roll a six-sided die.
The player has to guess the number, specifying the number as argument. To win, the player has to
match the number with the die. Let's see an example:
```
> roll 3
You roll a die. It lands on the number 4.
You played 3, you have lost.
> dice 1
You roll a die. It lands on the number 2.
You played 1, you have lost.
> dice 1
You roll a die. It lands on the number 1.
You played 1, you have won!
```
If that's your first command, it's a good opportunity to try to write it. A command with a simple
and finite role always is a good starting choice. Here's how we could (first) write it... but it
won't work as is, I warn you:
```python
from random import randint
from evennia import Command
class CmdRoll(Command):
"""
Play random, enter a number and try your luck.
Usage:
roll <number>
Enter a valid number as argument. A random die will be rolled and you
will win if you have specified the correct number.
Example:
roll 3
"""
key = "roll"
def parse(self):
"""Convert the argument to a number."""
self.args = self.args.lstrip()
def func(self):
# Roll a random die
figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both
self.msg(f"You roll a die. It lands on the number {figure}.")
if self.args == figure: # THAT WILL BREAK!
self.msg(f"You played {self.args}, you have won!")
else:
self.msg(f"You played {self.args}, you have lost.")
```
If you try this code, Python will complain that you try to compare a number with a string: `figure`
is a number and `self.args` is a string and can't be compared as-is in Python. Python doesn't do
"implicit converting" as some languages do. By the way, this might be annoying sometimes, and other
times you will be glad it tries to encourage you to be explicit rather than implicit about what to
do. This is an ongoing debate between programmers. Let's move on!
So we need to convert the command argument from a `str` into an `int`. There are a few ways to do
it. But the proper way is to try to convert and deal with the `ValueError` Python exception.
Converting a `str` into an `int` in Python is extremely simple: just use the `int` function, give it
the string and it returns an integer, if it could. If it can't, it will raise `ValueError`. So
we'll need to catch that. However, we also have to indicate to Evennia that, should the number be
invalid, no further parsing should be done. Here's a new attempt at our command with this
converting:
```python
from random import randint
from evennia import Command, InterruptCommand
class CmdRoll(Command):
"""
Play random, enter a number and try your luck.
Usage:
roll <number>
Enter a valid number as argument. A random die will be rolled and you
will win if you have specified the correct number.
Example:
roll 3
"""
key = "roll"
def parse(self):
"""Convert the argument to number if possible."""
args = self.args.lstrip()
# Convert to int if possible
# If not, raise InterruptCommand. Evennia will catch this
# exception and not call the 'func' method.
try:
self.entered = int(args)
except ValueError:
self.msg(f"{args} is not a valid number.")
raise InterruptCommand
def func(self):
# Roll a random die
figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both
self.msg(f"You roll a die. It lands on the number {figure}.")
if self.entered == figure:
self.msg(f"You played {self.entered}, you have won!")
else:
self.msg(f"You played {self.entered}, you have lost.")
```
Before enjoying the result, let's examine the `parse` method a little more: what it does is try to
convert the entered argument from a `str` to an `int`. This might fail (if a user enters `roll
something`). In such a case, Python raises a `ValueError` exception. We catch it in our
`try/except` block, send a message to the user and raise the `InterruptCommand` exception in
response to tell Evennia to not run `func()`, since we have no valid number to give it.
In the `func` method, instead of using `self.args`, we use `self.entered` which we have defined in
our `parse` method. You can expect that, if `func()` is run, then `self.entered` contains a valid
number.
If you try this command, it will work as expected this time: the number is converted as it should
and compared to the die roll. You might spend some minutes playing this game. Time out!
Something else we could want to address: in our small example, we only want the user to enter a
positive number between 1 and 6. And the user can enter `roll 0` or `roll -8` or `roll 208` for
that matter, the game still works. It might be worth addressing. Again, you could write a
condition to do that, but since we're catching an exception, we might end up with something cleaner
by grouping:
```python
from random import randint
from evennia import Command, InterruptCommand
class CmdRoll(Command):
"""
Play random, enter a number and try your luck.
Usage:
roll <number>
Enter a valid number as argument. A random die will be rolled and you
will win if you have specified the correct number.
Example:
roll 3
"""
key = "roll"
def parse(self):
"""Convert the argument to number if possible."""
args = self.args.lstrip()
# Convert to int if possible
try:
self.entered = int(args)
if not 1 <= self.entered <= 6:
# self.entered is not between 1 and 6 (including both)
raise ValueError
except ValueError:
self.msg(f"{args} is not a valid number.")
raise InterruptCommand
def func(self):
# Roll a random die
figure = randint(1, 6) # return a pseudo-random number between 1 and 6, including both
self.msg(f"You roll a die. It lands on the number {figure}.")
if self.entered == figure:
self.msg(f"You played {self.entered}, you have won!")
else:
self.msg(f"You played {self.entered}, you have lost.")
```
Using grouped exceptions like that makes our code easier to read, but if you feel more comfortable
checking, afterward, that the number the user entered is in the right range, you can do so in a
latter condition.
> Notice that we have updated our `parse` method only in this last attempt, not our `func()` method
which remains the same. This is one goal of separating argument parsing from command processing,
these two actions are best kept isolated.
### Working with several arguments
Often a command expects several arguments. So far, in our example with the "roll" command, we only
expect one argument: a number and just a number. What if we want the user to specify several
numbers? First the number of dice to roll, then the guess?
> You won't win often if you roll 5 dice but that's for the example.
So we would like to interpret a command like this:
> roll 3 12
(To be understood: roll 3 dice, my guess is the total number will be 12.)
What we need is to cut our command argument, which is a `str`, break it at the space (we use the
space as a delimiter). Python provides the `str.split` method which we'll use. Again, here are
some examples from the Python interpreter:
>>> args = "3 12"
>>> args.split(" ")
['3', '12']
>>> args = "a command with several arguments"
>>> args.split(" ")
['a', 'command', 'with', 'several', 'arguments']
>>>
As you can see, `str.split` will "convert" our strings into a list of strings. The specified
argument (`" "` in our case) is used as delimiter. So Python browses our original string. When it
sees a delimiter, it takes whatever is before this delimiter and append it to a list.
The point here is that `str.split` will be used to split our argument. But, as you can see from the
above output, we can never be sure of the length of the list at this point:
>>> args = "something"
>>> args.split(" ")
['something']
>>> args = ""
>>> args.split(" ")
['']
>>>
Again we could use a condition to check the number of split arguments, but Python offers a better
approach, making use of its exception mechanism. We'll give a second argument to `str.split`, the
maximum number of splits to do. Let's see an example, this feature might be confusing at first
glance:
>>> args = "that is something great"
>>> args.split(" ", 1) # one split, that is a list with two elements (before, after)
['that', 'is something great']
>>>
Read this example as many times as needed to understand it. The second argument we give to
`str.split` is not the length of the list that should be returned, but the number of times we have
to split. Therefore, we specify 1 here, but we get a list of two elements (before the separator,
after the separator).
> What will happen if Python can't split the number of times we ask?
It won't:
>>> args = "whatever"
>>> args.split(" ", 1) # there isn't even a space here...
['whatever']
>>>
This is one moment I would have hoped for an exception and didn't get one. But there's another way
which will raise an exception if there is an error: variable unpacking.
We won't talk about this feature in details here. It would be complicated. But the code is really
straightforward to use. Let's take our example of the roll command but let's add a first argument:
the number of dice to roll.
```python
from random import randint
from evennia import Command, InterruptCommand
class CmdRoll(Command):
"""
Play random, enter a number and try your luck.
Specify two numbers separated by a space. The first number is the
number of dice to roll (1, 2, 3) and the second is the expected sum
of the roll.
Usage:
roll <dice> <number>
For instance, to roll two 6-figure dice, enter 2 as first argument.
If you think the sum of these two dice roll will be 10, you could enter:
roll 2 10
"""
key = "roll"
def parse(self):
"""Split the arguments and convert them."""
args = self.args.lstrip()
# Split: we expect two arguments separated by a space
try:
number, guess = args.split(" ", 1)
except ValueError:
self.msg("Invalid usage. Enter two numbers separated by a space.")
raise InterruptCommand
# Convert the entered number (first argument)
try:
self.number = int(number)
if self.number <= 0:
raise ValueError
except ValueError:
self.msg(f"{number} is not a valid number of dice.")
raise InterruptCommand
# Convert the entered guess (second argument)
try:
self.guess = int(guess)
if not 1 <= self.guess <= self.number * 6:
raise ValueError
except ValueError:
self.msg(f"{self.guess} is not a valid guess.")
raise InterruptCommand
def func(self):
# Roll a random die X times (X being self.number)
figure = 0
for _ in range(self.number):
figure += randint(1, 6)
self.msg(f"You roll {self.number} dice and obtain the sum {figure}.")
if self.guess == figure:
self.msg(f"You played {self.guess}, you have won!")
else:
self.msg(f"You played {self.guess}, you have lost.")
```
The beginning of the `parse()` method is what interests us most:
```python
try:
number, guess = args.split(" ", 1)
except ValueError:
self.msg("Invalid usage. Enter two numbers separated by a space.")
raise InterruptCommand
```
We split the argument using `str.split` but we capture the result in two variables. Python is smart
enough to know that we want what's left of the space in the first variable, what's right of the
space in the second variable. If there is not even a space in the string, Python will raise a
`ValueError` exception.
This code is much easier to read than browsing through the returned strings of `str.split`. We can
convert both variables the way we did previously. Actually there are not so many changes in this
version and the previous one, most of it is due to name changes for clarity.
> Splitting a string with a maximum of splits is a common occurrence while parsing command
arguments. You can also see the `str.rspli8t` method that does the same thing but from the right of
the string. Therefore, it will attempt to find delimiters at the end of the string and work toward
the beginning of it.
We have used a space as a delimiter. This is absolutely not necessary. You might remember that
most default Evennia commands can take an `=` sign as a delimiter. Now you know how to parse them
as well:
>>> cmd_key = "tel"
>>> cmd_args = "book = chest"
>>> left, right = cmd_args.split("=") # mighht raise ValueError!
>>> left
'book '
>>> right
' chest'
>>>
### Optional arguments
Sometimes, you'll come across commands that have optional arguments. These arguments are not
necessary but they can be set if more information is needed. I will not provide the entire command
code here but just enough code to show the mechanism in Python:
Again, we'll use `str.split`, knowing that we might not have any delimiter at all. For instance,
the player could enter the "tel" command like this:
> tel book
> tell book = chest
The equal sign is optional along with whatever is specified after it. A possible solution in our
`parse` method would be:
```python
def parse(self):
args = self.args.lstrip()
# = is optional
try:
obj, destination = args.split("=", 1)
except ValueError:
obj = args
destination = None
```
This code would place everything the user entered in `obj` if she didn't specify any equal sign.
Otherwise, what's before the equal sign will go in `obj`, what's after the equal sign will go in
`destination`. This makes for quick testing after that, more robust code with less conditions that
might too easily break your code if you're not careful.
> Again, here we specified a maximum numbers of splits. If the users enters:
> tel book = chest = chair
Then `destination` will contain: `" chest = chair"`. This is often desired, but it's up to you to
set parsing however you like.
## Evennia searches
After this quick tour of some `str` methods, we'll take a look at some Evennia-specific features
that you won't find in standard Python.
One very common task is to convert a `str` into an Evennia object. Take the previous example:
having `"book"` in a variable is great, but we would prefer to know what the user is talking
about... what is this `"book"`?
To get an object from a string, we perform an Evennia search. Evennia provides a `search` method on
all typeclassed objects (you will most likely use the one on characters or accounts). This method
supports a very wide array of arguments and has [its own tutorial](Part1/Searching-Things).
Some examples of useful cases follow:
### Local searches
When an account or a character enters a command, the account or character is found in the `caller`
attribute. Therefore, `self.caller` will contain an account or a character (or a session if that's
a session command, though that's not as frequent). The `search` method will be available on this
caller.
Let's take the same example of our little "tel" command. The user can specify an object as
argument:
```python
def parse(self):
name = self.args.lstrip()
```
We then need to "convert" this string into an Evennia object. The Evennia object will be searched
in the caller's location and its contents by default (that is to say, if the command has been
entered by a character, it will search the object in the character's room and the character's
inventory).
```python
def parse(self):
name = self.args.lstrip()
self.obj = self.caller.search(name)
```
We specify only one argument to the `search` method here: the string to search. If Evennia finds a
match, it will return it and we keep it in the `obj` attribute. If it can't find anything, it will
return `None` so we need to check for that:
```python
def parse(self):
name = self.args.lstrip()
self.obj = self.caller.search(name)
if self.obj is None:
# A proper error message has already been sent to the caller
raise InterruptCommand
```
That's it. After this condition, you know that whatever is in `self.obj` is a valid Evennia object
(another character, an object, an exit...).
### Quiet searches
By default, Evennia will handle the case when more than one match is found in the search. The user
will be asked to narrow down and re-enter the command. You can, however, ask to be returned the
list of matches and handle this list yourself:
```python
def parse(self):
name = self.args.lstrip()
objs = self.caller.search(name, quiet=True)
if not objs:
# This is an empty list, so no match
self.msg(f"No {name!r} was found.")
raise InterruptCommand
self.obj = objs[0] # Take the first match even if there are several
```
All we have changed to obtain a list is a keyword argument in the `search` method: `quiet`. If set
to `True`, then errors are ignored and a list is always returned, so we need to handle it as such.
Notice in this example, `self.obj` will contain a valid object too, but if several matches are
found, `self.obj` will contain the first one, even if more matches are available.
### Global searches
By default, Evennia will perform a local search, that is, a search limited by the location in which
the caller is. If you want to perform a global search (search in the entire database), just set the
`global_search` keyword argument to `True`:
```python
def parse(self):
name = self.args.lstrip()
self.obj = self.caller.search(name, global_search=True)
```
## Conclusion
Parsing command arguments is vital for most game designers. If you design "intelligent" commands,
users should be able to guess how to use them without reading the help, or with a very quick peek at
said help. Good commands are intuitive to users. Better commands do what they're told to do. For
game designers working on MUDs, commands are the main entry point for users into your game. This is
no trivial. If commands execute correctly (if their argument is parsed, if they don't behave in
unexpected ways and report back the right errors), you will have happier players that might stay
longer on your game. I hope this tutorial gave you some pointers on ways to improve your command
parsing. There are, of course, other ways you will discover, or ways you are already using in your
code.

View file

@ -3,13 +3,13 @@
[prev lesson](../Starting-Part1) | [next lesson](./Tutorial-World-Introduction)
In this lesson we will test out what we can do in-game out-of-the-box. Evennia ships with
[around 90 default commands](../../../Component/Default-Command-Help), and while you can override those as you please,
[around 90 default commands](../../../Components/Default-Command-Help), 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
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](../../../Concept/Using-MUX-as-a-Standard):
The default commands has syntax [similar to MUX](../../../Concepts/Using-MUX-as-a-Standard):
command[/switch/switch...] [arguments ...]
@ -127,14 +127,14 @@ dropped in the room, then try this:
lock box = get:false()
Locks represent a rather [big topic](../../../Component/Locks), but for now that will do what we want. This will lock
Locks represent a rather [big topic](../../../Components/Locks), 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](../../../Component/Attributes)
Think thís default error message looks dull? The `get` command looks for an [Attribute](../../../Components/Attributes)
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
@ -156,7 +156,7 @@ later, in the [Commands tutorial](./Adding-Commands).
## Get a Personality
[Scripts](../../../Component/Scripts) are powerful out-of-character objects useful for many "under the hood" things.
[Scripts](../../../Components/Scripts) 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:
@ -185,14 +185,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](../../../Component/Scripts) page explains more details.
the Python path to your script file. The [Scripts](../../../Components/Scripts) 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](../../../Component/Typeclasses), [Scripts](../../../Component/Scripts)
and object-based [Commands](../../../Component/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), [Scripts](../../../Components/Scripts)
and object-based [Commands](../../../Components/Commands), 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
@ -208,7 +208,7 @@ 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](../../../Component/Typeclasses) and [Commands](../../../Component/Commands) controlling it are
that the [Typeclass](../../../Components/Typeclasses) and [Commands](../../../Components/Commands) controlling it are
inside [evennia/contrib/tutorial_examples](api:evennia.contrib.tutorial_examples)
If you wait for a while (make sure you dropped it!) the button will blink invitingly.

View file

@ -1,4 +1,4 @@
## Django Database queries
# Django Database queries
[prev lesson](./Searching-Things) | [next lesson](../Starting-Part2)
@ -184,7 +184,7 @@ will_transform = (
Running this query makes our newly lycantrrophic Character appear in `will_transform`. Success!
> Don't confuse database fields with [Attributes](../../../Component/Attributes) you set via `obj.db.attr = 'foo'` or
> Don't confuse database fields with [Attributes](../../../Components/Attributes) you set via `obj.db.attr = 'foo'` or
`obj.attributes.add()`. Attributes are custom database entities *linked* to an object. They are not
separate fields *on* that object like `db_key` or `db_location` are.
@ -390,7 +390,7 @@ in a format like the following:
]
```
# 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.

View file

@ -53,25 +53,25 @@ This the the structure of the Evennia library:
- evennia
- [`__init__.py`](../../../Evennia-API#shortcuts) - The "flat API" of Evennia resides here.
- [`settings_default.py`](../../../Component/Server-Conf#Settings-file) - Root settings of Evennia. Copy settings
- [`settings_default.py`](../../../Components/Server-Conf#Settings-file) - Root settings of Evennia. Copy settings
from here to `mygame/server/settings.py` file.
- [`commands/`](../../../Component/Commands) - The command parser and handler.
- `default/` - The [default commands](../../../Component/Default-Command-Help) and cmdsets.
- [`comms/`](../../../Component/Communications) - Systems for communicating in-game.
- [`commands/`](../../../Components/Commands) - The command parser and handler.
- `default/` - The [default commands](../../../Components/Default-Command-Help) and cmdsets.
- [`comms/`](../../../Components/Communications) - Systems for communicating in-game.
- `contrib/` - Optional plugins too game-specific for core Evennia.
- `game_template/` - Copied to become the "game directory" when using `evennia --init`.
- [`help/`](../../../Component/Help-System) - Handles the storage and creation of help entries.
- `locale/` - Language files ([i18n](../../../Concept/Internationalization)).
- [`locks/`](../../../Component/Locks) - Lock system for restricting access to in-game entities.
- [`objects/`](../../../Component/Objects) - In-game entities (all types of items and Characters).
- [`prototypes/`](../../../Component/Spawner-and-Prototypes) - Object Prototype/spawning system and OLC menu
- [`accounts/`](../../../Component/Accounts) - Out-of-game Session-controlled entities (accounts, bots etc)
- [`scripts/`](../../../Component/Scripts) - Out-of-game entities equivalence to Objects, also with timer support.
- [`server/`](../../../Component/Portal-And-Server) - Core server code and Session handling.
- [`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/Spawner-and-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.
- `portal/` - Portal proxy and connection protocols.
- [`typeclasses/`](../../../Component/Typeclasses) - Abstract classes for the typeclass storage and database system.
- [`utils/`](../../../Component/Coding-Utils) - Various miscellaneous useful coding resources.
- [`web/`](../../../Concept/Web-Features) - Web resources and webserver. Partly copied into game directory on initialization.
- [`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.
```sidebar:: __init__.py

View file

@ -59,7 +59,7 @@ and how you point to it correctly.
## commands/
The `commands/` folder holds Python modules related to creating and extending the [Commands](../../../Component/Commands)
The `commands/` folder holds Python modules related to creating and extending the [Commands](../../../Components/Commands)
of Evennia. These manifest in game like the server understanding input like `look` or `dig`.
```sidebar:: Classes
@ -151,28 +151,28 @@ knows where they are and will read them to configure itself at startup.
### typeclasses/
The [Typeclasses](../../../Component/Typeclasses) of Evennia are Evennia-specific Python classes whose instances save themselves
The [Typeclasses](../../../Components/Typeclasses) 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](../../../Component/Accounts) represents the player connecting to the game. It holds information like email,
[Account](../../../Components/Accounts) 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](../../../Component/Channels) are used to manage in-game communication between players.
[Channels](../../../Components/Channels) are used to manage in-game communication between players.
- [objects.py](github:evennia/game_template/typeclasses/objects.py) (Python-path: `typeclasses.objects`) -
[Objects](../../../Component/Objects) represent all things having a location within the game world.
[Objects](../../../Components/Objects) 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](../../../Component/Objects#Characers) is a subclass of Objects, controlled by Accounts - they are the player's
The [Character](../../../Components/Objects#Characers) 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](../../../Component/Objects#Room) is also a subclass of Object; describing discrete locations. While the traditional
[Room](../../../Components/Objects#Room) 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](../../../Component/Objects#Exit) is another subclass of Object. Exits link one Room to another.
[Exits](../../../Components/Objects#Exit) 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](../../../Component/Scripts) are 'out-of-character' objects. They have no location in-game and can serve as basis for
[Scripts](../../../Components/Scripts) 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.
@ -203,7 +203,7 @@ 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](../../../Component/Spawner-and-Prototypes) is a way
- [prototypes.py](github:evennia/game_template/world/prototypes.py) - A [prototype](../../../Components/Spawner-and-Prototypes) 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

@ -99,12 +99,12 @@ yourself and what you get back is now a list of zero, one or more matches!
These are the main database entities one can search for:
- [Objects](../../../Component/Objects)
- [Accounts](../../../Component/Accounts)
- [Scripts](../../../Component/Scripts),
- [Channels](../../../Component/Communications#channels),
- [Objects](../../../Components/Objects)
- [Accounts](../../../Components/Accounts)
- [Scripts](../../../Components/Scripts),
- [Channels](../../../Components/Communications#channels),
- [Messages](Communication#Msg)
- [Help Entries](../../../Component/Help-System).
- [Help Entries](../../../Components/Help-System).
Most of the time you'll likely spend your time searching for Objects and the occasional Accounts.
@ -134,7 +134,7 @@ general search function. If we assume `room` is a particular Room instance,
### Search by Tags
Think of a [Tag](../../../Component/Tags) as the label the airport puts on your luggage when flying.
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
go to which plane. Entities in Evennia can be grouped in the same way. Any number of tags can be attached
to each object.
@ -168,7 +168,7 @@ This gets all three books.
### Search by Attribute
We can also search by the [Attributes](../../../Component/Attributes) associated with entities.
We can also search by the [Attributes](../../../Components/Attributes) associated with entities.
For example, let's give our rose thorns:

View file

@ -125,18 +125,18 @@ gloss over this bit and jump directly to **World Building**. Vice versa, many "g
tend to jump directly to this part without doing the **Planning** first. Neither way is good and
*will* lead to you having to redo all your hard work at least once, probably more.
Evennia's [Evennia Component overview](../../../Component/Component-Overview) tries to help you with this bit of development. We
Evennia's [Evennia Component overview](../../../Components/Components-Overview) tries to help you with this bit of development. We
also have a slew of [Tutorials](../../Howto-Overview) with worked examples. Evennia tries hard to make this
part easier for you, but there is no way around the fact that if you want anything but a very basic
Talker-type game 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 of Evennia, such as [Objects](../../../Component/Objects),
[Commands](../../../Component/Commands) and [Scripts](../../../Component/Scripts) and
paradigms of Evennia, such as [Objects](../../../Components/Objects),
[Commands](../../../Components/Commands) and [Scripts](../../../Components/Scripts) and
how they hang together. We recommend you go through the [Tutorial World](../Part1/Tutorial-World-Introduction) in detail (as well as glancing at its code) to get at least a feel for what is
involved behind the scenes. You could also look through the tutorial for
[building a game from scratch](../Tutorial-for-basic-MUSH-like-game).
[building a game from scratch](../Part3/Tutorial-for-basic-MUSH-like-game).
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.

View file

@ -0,0 +1,3 @@
# Some useful contribs
TODO

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](../../Component/Attributes), like `character.db.STR=34` and
choose to either store things as individual [Attributes](../../../Components/Attributes), 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](../../Concept/New-Models). Which is the better depends on your game and the
with a [custom django model](../../../Concepts/New-Models). Which is the better depends on your game and the
complexity of your system.
- Make a clear [API](http://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

@ -31,7 +31,7 @@ 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](../../Component/Commands), possibly ones with a [cooldown](../Command-Cooldown). You also need a [game rule
[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
variety here.
@ -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](../../Component/Scripts) object
- A combat handler. This is the main mechanic of the system. This is a [Script](../../../Components/Scripts) 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](../../Component/Command-Sets) with the relevant commands needed for combat, such as the
- A combat [command set](../../../Components/Command-Sets) 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
combat resolution.
- An `attack` [command](../../Component/Commands) for initiating the combat mode. This is added to the default
- An `attack` [command](../../../Components/Commands) 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](../../Component/Scripts). This Script is created when
The _combat handler_ is implemented as a stand-alone [Script](../../../Components/Scripts). 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](../../Component/TickerHandler). This would require either adding custom hook
time-keeping with the [TickerHandler](../../../Components/TickerHandler). 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._
@ -506,7 +506,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) 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](./Starting-Part1) tutorial
The tutorial starts from scratch. If you did the [First Steps Coding](../Starting-Part1) 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](../../Component/Attributes) `power` and `combat_score` and set them to default
We defined two new [Attributes](../../../Components/Attributes) `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](../../Component/Commands) to set the "Power" on the `Character`.
- A chargen [CmdSet](../../Component/Command-Sets) to hold this command. Lets call it `ChargenCmdset`.
- 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`.
- 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](../../Component/Commands) to set the
For this tutorial character generation will only consist of one [Command](../../../Components/Commands) 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](../../Component/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) 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 `permanent=True` keyword or you will lose the cmdset after a server reload. For
more information about [Command Sets](../../Component/Command-Sets) and [Commands](../../Component/Commands), see the respective
more information about [Command Sets](../../../Components/Command-Sets) and [Commands](../../../Components/Commands), 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](../../Component/Attributes) has been set correctly.
user) to check so the `Power` [Attribute](../../../Components/Attributes) 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
@ -391,7 +391,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](../../Component/Locks#Permissions).
[Permissions and Locks](../../../Components/Locks#Permissions).
### Creating an NPC with +createNPC
@ -443,13 +443,13 @@ class CmdCreateNPC(Command):
exclude=caller)
```
Here we define a `+createnpc` (`+createNPC` works too) that is callable by everyone *not* having the
`nonpcs` "[permission](../../Component/Locks#Permissions)" (in Evennia, a "permission" can just as well be used to
`nonpcs` "[permission](../../../Components/Locks#Permissions)" (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](../../Component/Locks) for more information about the lock system.
[Locks](../../../Components/Locks) 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
@ -464,7 +464,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](../../Component/Attributes) stored on the NPC object. So as a Builder or Admin we could set this
simple [Attribute](../../../Components/Attributes) 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
@ -649,6 +649,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](../../Component/Component-Overview).
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).

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) 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](http://en.wikipedia.org/wiki/CSS), [Javascript](http://en.wikipedia.o
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](../../Concept/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) 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). 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) 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.

View file

@ -1,43 +1,51 @@
# Evennia Starting Tutorial (Part 1)
# Starting Tutorial (Part 1)
[Next lesson](Part1/Building-Quickstart)
[Start](Part1/Building-Quickstart)
This is a multi-part Tutorial that will gradually take you from first installation to making your
own first little game in Evennia. Let's get started!
```sidebar:: Tutorial Parts
```sidebar:: Parts of the Starting tutorial
**Part 1**: What we have
**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 <Starting-Part2>`_
Part 2: `What we want <./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 <Starting-Part3>`_
Part 3: `How we get there <./Starting-Part3.html>`_
Getting down to the meat of extending Evennia to make our game
Part 4: `Using what we created <Starting-Part4>`_
Part 4: `Using what we created <./Starting-Part4.html>`_
Building a tech-demo and world content to go with our code
Part 5: `Showing the world <Starting-Part5>`_
Part 5: `Showing the world <./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!
## Lessons of Part 1 - "What we have"
1. Introduction & Overview (you are here)
1. [Building stuff](Part1/Building-Quickstart)
1. [The Tutorial World](Part1/Tutorial-World-Introduction)
1. [Python basics](Part1/Python-basic-introduction)
1. [Game dir overview](Part1/Gamedir-Overview)
1. [Python classes and objects](Part1/Python-classes-and-objects)
1. [Accessing the Evennia library](Part1/Evennia-Library-Overview)
1. [Typeclasses - Persistent objects](Part1/Learning-Typeclasses)
1. [Making our first own commands](Part1/Adding-Commands)
1. [Parsing and replacing default Commands](Part1/More-on-Commands)
1. [Creating things](Part1/Creating-Things)
1. [Searching for things](Part1/Searching-Things)
1. [Advanced searching with Django queries](Part1/Django-queries)
```toctree::
:numbered:
:maxdepth: 1
Building stuff <Part1/Building-Quickstart>
The Tutorial World <Part1/Tutorial-World-Introduction>
Python basics <Part1/Python-basic-introduction>
Game dir overview <Part1/Gamedir-Overview>
Python classes and objects <Part1/Python-classes-and-objects>
Accessing the Evennia library <Part1/Evennia-Library-Overview>
Typeclasses and Persistent objects <Part1/Learning-Typeclasses>
Making first own Commands <Part1/Adding-Commands>
Parsing and replacing default Commands <Part1/More-on-Commands>
Creating things <Part1/Creating-Things>
Searching for things <Part1/Searching-Things>
Advanced searching with Django queries <Part1/Django-queries>
```
In this first part we'll focus on what we get out of the box in Evennia - we'll get used to the tools,
where things are and how we find things we are looking for. We will also dive into some of things you'll
need to know to fully utilize the system, including giving a brief rundown of Python concepts.
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.
## Things you will need
@ -106,4 +114,4 @@ first enter that gamedir and run
You should now be good to go!
[Next lesson](Part1/Building-Quickstart)
[Start](Part1/Building-Quickstart)

View file

@ -1,17 +1,28 @@
# Evennia Starting Tutorial (Part 2)
```sidebar:: Parts of the Starting tutorial
```sidebar:: Tutorial Parts
**Part 1**: What we have
Part 1: `What we have <./Starting-Part1.html>`_
A tour of Evennia and how to use the tools, including an introduction to Python.
Part 2: `What we want <Starting-Part2>`_
**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 <Starting-Part3>`_
Part 3: `How we get there <./Starting-Part3.html>`_
Getting down to the meat of extending Evennia to make our game
Part 4: `Using what we created <Starting-Part4>`_
Part 4: `Using what we created <./Starting-Part4.html>`_
Building a tech-demo and world content to go with our code
Part 5: `Showing the world <Starting-Part5>`_
Part 5: `Showing the world <./Starting-Part5.html>`_
Taking our new game online and let players try it out
```
## Lessons for Part 2
1. Introduction & Overview (you are here)
1. [On planning a game](Part2/Game-Planning)
1. [Multisession modes](../../Unimplemented)
1. [Layout of our tutorial game](../../Unimplemented)
1. [Some useful Contribs](Part2/Some-Useful-Contribs)
In Part two of the Starting tutorial we'll step back and plan out the kind of tutorial
game we want to make. 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. We'll also look at
some useful Evennia settings to tweak and designs to consider.

View file

@ -1 +1,19 @@
# Evennia Starting Tutorial (Part 3)
# Evennia Starting Tutorial (Part 3)
```sidebar:: Tutorial Parts
Part 1: `What we have <./Starting-Part1.html>`_
A tour of Evennia and how to use the tools, including an introduction to Python.
Part 2: `What we want <./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**
Getting down to the meat of extending Evennia to make our game
Part 4: `Using what we created <./Starting-Part4.html>`_
Building a tech-demo and world content to go with our code
Part 5: `Showing the world <./Starting-Part5.html>`_
Taking our new game online and let players try it out
```
Now that we have a good idea of what we want, we need to actually implement it. In part three of the
Starting tutorial will go through the creation of several key parts of our game. As we go, we will
test each part and create a simple "tech demo" to show off all the moving parts.

View file

@ -1,3 +1,21 @@
# Evennia Starting Tutorial (Part 4)
TODO.
```sidebar:: Tutorial Parts
Part 1: `What we have <./Starting-Part1.html>`_
A tour of Evennia and how to use the tools, including an introduction to Python.
Part 2: `What we want <./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 <./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 <./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
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.
In part four we will expand our tech demo into a more full-fledged (if small) game by use of batchcommand
and batchcode processors.

View file

@ -1 +1,19 @@
# Evennia Starting Tutorial (part 5)
# Evennia Starting Tutorial (part 5)
```sidebar:: Tutorial Parts
Part 1: `What we have <./Starting-Part1.html>`_
A tour of Evennia and how to use the tools, including an introduction to Python.
Part 2: `What we want <./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 <./Starting-Part3.html>`_
Getting down to the meat of extending Evennia to make our game
Part 4: `Using what we created <./Starting-Part4.html>`_
Building a tech-demo and world content to go with our code
**Part 5: Showing the world**
Taking our new game online and let players try it out
```
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.