mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Doc refactor/renaming
This commit is contained in:
parent
9d8e8d7693
commit
b5b265ec3b
115 changed files with 518 additions and 434 deletions
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
3
docs/source/Howto/Starting/Part2/Some-Useful-Contribs.md
Normal file
3
docs/source/Howto/Starting/Part2/Some-Useful-Contribs.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Some useful contribs
|
||||
|
||||
TODO
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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).
|
||||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue