Refactored Contrib docs, renamed many files

This commit is contained in:
Griatch 2022-11-23 21:15:23 +01:00
parent 7845369d50
commit da341af663
44 changed files with 664 additions and 917 deletions

View file

@ -1,114 +0,0 @@
# Beginner Tutorial
```{sidebar} Beginner Tutorial Parts
- **[Introduction](./Beginner-Tutorial-Intro.md)**
<br>Getting set up.
- Part 1: [What we have](Part1/Beginner-Tutorial-Part1-Intro.md)
<br>A tour of Evennia and how to use the tools, including an introduction to Python.
- Part 2: [What we want](Part2/Beginner-Tutorial-Part2-Intro.md)
<br>Planning our tutorial game and what to think about when planning your own in the future.
- Part 3: [How we get there](Part3/Beginner-Tutorial-Part3-Intro.md)
<br>Getting down to the meat of extending Evennia to make our game
- Part 4: [Using what we created](Part4/Beginner-Tutorial-Part4-Intro.md)
<br>Building a tech-demo and world content to go with our code
- Part 5: [Showing the world](Part5/Beginner-Tutorial-Part5-Intro.md)
<br>Taking our new game online and let players try it out
```
Welcome to Evennia! This multi-part Beginner 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!
Use the menu on the right to get the index of each tutorial-part. Use the [next](Part1/Beginner-Tutorial-Part1-Intro.md)
and [previous](../Howtos-Overview.md) links to step from lesson to lesson.
## Things you need
- A Command line
- A MUD client (or web browser)
- A text-editor/IDE
- Evennia installed and a game-dir initialized
### A Command line
You need to know how to find your Terminal/Console in your OS. The Evennia server can be controlled
from in-game, but you _will_ need to use the command-line to get anywhere. Here are some starters:
- [Django-girls' Intro to the Command line for different OS:es](https://tutorial.djangogirls.org/en/intro_to_command_line/)
Note that we usually only show forward-slashes `/` for file system paths. Windows users should mentally convert this to
back-slashes `\` instead.
### A MUD client
You might already have a MUD-client you prefer. Check out the [grid of supported clients](../../Setup/Client-Support-Grid.md) for aid.
If telnet's not your thing, you can also just use Evennia's web client in your browser.
> In this documentation we often use the terms 'MUD', 'MU' or 'MU*' interchangeably
to represent all the historically different forms of text-based multiplayer game-styles,
like MUD, MUX, MUSH, MUCK, MOO and others. Evennia can be used to create all those game-styles
and more.
### An Editor
You need a text-editor to edit Python source files. Most everything that can edit and output raw
text works (so not Word).
- [Here's a blog post summing up some of the alternatives](https://www.elegantthemes.com/blog/resources/best-code-editors) - these
things don't change much from year to year. Popular choices for Python are PyCharm, VSCode, Atom, Sublime Text and Notepad++.
Evennia is to a very large degree coded in VIM, but that's not suitable for beginners.
> Hint: When setting up your editor, make sure that pressing TAB inserts _4 spaces_ rather than a Tab-character. Since
> Python is whitespace-aware, this will make your life a lot easier.
### Set up a game dir for the tutorial
Next you should make sure you have [installed Evennia](../../Setup/Installation.md). If you followed the instructions
you will already have created a game-dir. You could use that for this tutorial or you may want to do the
tutorial in its own, isolated game dir; it's up to you.
- If you want a new gamedir for the tutorial game and already have Evennia running with another gamedir,
first enter that gamedir and run
evennia stop
> If you want to run two parallel servers, that'd be fine too, but one would have to use
> different ports from the defaults, or there'd be a clash. We will go into changing settings later.
- Now go to where you want to create your tutorial-game. We will always refer to it as `mygame` so
it may be convenient if you do too:
evennia --init mygame
cd mygame
evennia migrate
evennia start --log
Add your superuser name and password at the prompt (email is optional). Make sure you can
go to `localhost:4000` in your MUD client or to [http://localhost:4001](http://localhost:4001)
in your web browser (Mac users: Try `127.0.0.1` instead of `localhost` if you have trouble).
The above `--log` flag will have Evennia output all its logs to the terminal. This will block
the terminal from other input. To leave the log-view, press `Ctrl-C` (`Cmd-C` on Mac). To see
the log again just run
evennia --log
You should now be good to go on to [the first part of the tutorial](Part1/Beginner-Tutorial-Part1-Intro.md).
Good luck!
<details>
<summary>
Click here to expand a list of all Beginner-Tutorial sections (all parts).
</summary>
```{toctree}
Part1/Beginner-Tutorial-Part1-Intro
Part2/Beginner-Tutorial-Part2-Intro
Part3/Beginner-Tutorial-Part3-Intro
Part4/Beginner-Tutorial-Part4-Intro
Part5/Beginner-Tutorial-Part5-Intro
```
</details>

View file

@ -0,0 +1,82 @@
# Beginner Tutorial
```{sidebar} Beginner Tutorial Parts
- **[Introduction](./Beginner-Tutorial-Overview.md)**
<br>Getting set up.
- Part 1: [What we have](Part1/Beginner-Tutorial-Part1-Overview.md)
<br>A tour of Evennia and how to use the tools, including an introduction to Python.
- Part 2: [What we want](Part2/Beginner-Tutorial-Part2-Overview.md)
<br>Planning our tutorial game and what to think about when planning your own in the future.
- Part 3: [How we get there](Part3/Beginner-Tutorial-Part3-Overview.md)
<br>Getting down to the meat of extending Evennia to make our game
- Part 4: [Using what we created](Part4/Beginner-Tutorial-Part4-Overview.md)
<br>Building a tech-demo and world content to go with our code
- Part 5: [Showing the world](Part5/Beginner-Tutorial-Part5-Overview.md)
<br>Taking our new game online and let players try it out
```
Welcome to Evennia! This multi-part Beginner Tutorial will help you get off the ground.
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!
Use the menu on the right to get the index of each tutorial-part. Use the [next](Part1/Beginner-Tutorial-Part1-Overview.md) and [previous](../Howtos-Overview.md) links at the top/bottom right of the page to step between lessons.
## Things you need
- A Command line
- A MUD client (or web browser)
- A text-editor/IDE
- Evennia installed and a game-dir initialized
### A Command line
You need to know how to find your Terminal/Console in your OS. The Evennia server can be controlled from in-game, but you _will_ need to use the command-line to get anywhere. Here are some starters:
- [Online Intro to the Command line for different OS:es](https://tutorial.djangogirls.org/en/intro_to_command_line/)
> Note that we usually only show forward-slashes `/` for file system paths. Windows users should mentally convert this to back-slashes `\` instead.
### A MUD client
You might already have a MUD-client you prefer. Check out the [grid of supported clients](../../Setup/Client-Support-Grid.md).
If telnet's not your thing, you can also just use Evennia's web client in your browser.
> In this documentation we often use the terms 'MUD', 'MU' or 'MU*' interchangeably to represent all the historically different forms of text-based multiplayer game-styles, like MUD, MUX, MUSH, MUCK, MOO and others. Evennia can be used to create all those game-styles and more.
### A text Editor or IDE
You need a text-editor to edit Python source files. Most everything that can edit and output raw
text works (so not Word).
- [Here's a blog post summing up some of the alternatives](https://www.elegantthemes.com/blog/resources/best-code-editors) - these things don't change much from year to year. Popular choices for Python are PyCharm, VSCode, Atom, Sublime Text and Notepad++. Evennia is to a very large degree coded in VIM, but that's not suitable for beginners.
```{important} Use spaces, not tabs
```
> Make sure to configure your editor so that pressing TAB inserts _4 spaces_ rather than a Tab-character. Since Python is whitespace-aware, this will make your life a lot easier.
### A fresh game dir?
You should make sure you have [installed Evennia](../../Setup/Installation.md). If you followed the instructions you will already have created a game-dir.
You could re-use that or make a new one only for this tutorial, it's up to you.
If you already have a game dir and want a separate one for the tutorial, use `evennia stop` to halt the running server and then [Initialize a new game dir](../../Setup/Installation.md#initialize-a-new-game) somewhere else (_not_ inside the previous game dir!). We refer to it everywhere as `mygame`, so you may want to do the same.
You should now be ready to move on to the [first lesson](Part1/Beginner-Tutorial-Part1-Overview.md)
<details>
<summary>
Click here to expand a list of all Beginner-Tutorial sections (all parts).
</summary>
```{toctree}
Part1/Beginner-Tutorial-Part1-Overview
Part2/Beginner-Tutorial-Part2-Overview
Part3/Beginner-Tutorial-Part3-Overview
Part4/Beginner-Tutorial-Part4-Overview
Part5/Beginner-Tutorial-Part5-Overview
```
</details>

View file

@ -1,17 +1,17 @@
# Part 1: What we have
```{sidebar} Beginner Tutorial Parts
- [Introduction](../Beginner-Tutorial-Intro.md)
- [Introduction](../Beginner-Tutorial-Overview.md)
<br>Getting set up.
- Part 1: **[What we have](./Beginner-Tutorial-Part1-Intro.md)**
- Part 1: **[What we have](./Beginner-Tutorial-Part1-Overview.md)**
<br>A tour of Evennia and how to use the tools, including an introduction to Python.
- Part 2: [What we want](../Part2/Beginner-Tutorial-Part2-Intro.md)
- Part 2: [What we want](../Part2/Beginner-Tutorial-Part2-Overview.md)
<br>Planning our tutorial game and what to think about when planning your own in the future.
- Part 3: [How we get there](../Part3/Beginner-Tutorial-Part3-Intro.md)
- Part 3: [How we get there](../Part3/Beginner-Tutorial-Part3-Overview.md)
<br>Getting down to the meat of extending Evennia to make our game
- Part 4: [Using what we created](../Part4/Beginner-Tutorial-Part4-Intro.md)
- Part 4: [Using what we created](../Part4/Beginner-Tutorial-Part4-Overview.md)
<br>Building a tech-demo and world content to go with our code
- Part 5: [Showing the world](../Part5/Beginner-Tutorial-Part5-Intro.md)
- Part 5: [Showing the world](../Part5/Beginner-Tutorial-Part5-Overview.md)
<br>Taking our new game online and let players try it out
```

View file

@ -1,17 +1,17 @@
# Part 2: What we want
```{sidebar} Beginner Tutorial Parts
- [Introduction](../Beginner-Tutorial-Intro.md)
- [Introduction](../Beginner-Tutorial-Overview.md)
<br>Getting set up.
- Part 1: [What we have](../Part1/Beginner-Tutorial-Part1-Intro.md)
- Part 1: [What we have](../Part1/Beginner-Tutorial-Part1-Overview.md)
<br>A tour of Evennia and how to use the tools, including an introduction to Python.
- **Part 2: [What we want](./Beginner-Tutorial-Part2-Intro.md)**
- **Part 2: [What we want](./Beginner-Tutorial-Part2-Overview.md)**
<br>Planning our tutorial game and what to think about when planning your own in the future.
- Part 3: [How we get there](../Part3/Beginner-Tutorial-Part3-Intro.md)
- Part 3: [How we get there](../Part3/Beginner-Tutorial-Part3-Overview.md)
<br>Getting down to the meat of extending Evennia to make our game
- Part 4: [Using what we created](../Part4/Beginner-Tutorial-Part4-Intro.md)
- Part 4: [Using what we created](../Part4/Beginner-Tutorial-Part4-Overview.md)
<br>Building a tech-demo and world content to go with our code
- Part 5: [Showing the world](../Part5/Beginner-Tutorial-Part5-Intro.md)
- Part 5: [Showing the world](../Part5/Beginner-Tutorial-Part5-Overview.md)
<br>Taking our new game online and let players try it out
```

View file

@ -7,17 +7,17 @@ from it at this time.
```
```{sidebar} Beginner Tutorial Parts
- [Introduction](../Beginner-Tutorial-Intro.md)
- [Introduction](../Beginner-Tutorial-Overview.md)
<br>Getting set up.
- Part 1: [What we have](../Part1/Beginner-Tutorial-Part1-Intro.md)
- Part 1: [What we have](../Part1/Beginner-Tutorial-Part1-Overview.md)
<br>A tour of Evennia and how to use the tools, including an introduction to Python.
- Part 2: [What we want](../Part2/Beginner-Tutorial-Part2-Intro.md)
- Part 2: [What we want](../Part2/Beginner-Tutorial-Part2-Overview.md)
<br>Planning our tutorial game and what to think about when planning your own in the future.
- **Part 3: [How we get there](./Beginner-Tutorial-Part3-Intro.md)**
- **Part 3: [How we get there](./Beginner-Tutorial-Part3-Overview.md)**
<br>Getting down to the meat of extending Evennia to make our game
- Part 4: [Using what we created](../Part4/Beginner-Tutorial-Part4-Intro.md)
- Part 4: [Using what we created](../Part4/Beginner-Tutorial-Part4-Overview.md)
<br>Building a tech-demo and world content to go with our code
- Part 5: [Showing the world](../Part5/Beginner-Tutorial-Part5-Intro.md)
- Part 5: [Showing the world](../Part5/Beginner-Tutorial-Part5-Overview.md)
<br>Taking our new game online and let players try it out
```

View file

@ -1,34 +0,0 @@
# Part 4: Using what we created
```{sidebar} Beginner Tutorial Parts
- [Introduction](../Beginner-Tutorial-Intro.md)
<br>Getting set up.
- Part 1: [What we have](../Part1/Beginner-Tutorial-Part1-Intro.md)
<br>A tour of Evennia and how to use the tools, including an introduction to Python.
- Part 2: [What we want](../Part2/Beginner-Tutorial-Part2-Intro.md)
<br>Planning our tutorial game and what to think about when planning your own in the future.
- Part 3: [How we get there](../Part3/Beginner-Tutorial-Part3-Intro.md)
<br>Getting down to the meat of extending Evennia to make our game
- **Part 4: [Using what we created](./Beginner-Tutorial-Part4-Intro.md)**
<br>Building a tech-demo and world content to go with our code
- Part 5: [Showing the world](../Part5/Beginner-Tutorial-Part5-Intro.md)
<br>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.
## Lessons
_TODO_
```{toctree}
:numbered:
:maxdepth: 2
../../../Unimplemented.md
```

View file

@ -0,0 +1,30 @@
# Part 4: Using what we created
```{sidebar} Beginner Tutorial Parts
- [Introduction](../Beginner-Tutorial-Overview.md)
<br>Getting set up.
- Part 1: [What we have](../Part1/Beginner-Tutorial-Part1-Overview.md)
<br>A tour of Evennia and how to use the tools, including an introduction to Python.
- Part 2: [What we want](../Part2/Beginner-Tutorial-Part2-Overview.md)
<br>Planning our tutorial game and what to think about when planning your own in the future.
- Part 3: [How we get there](../Part3/Beginner-Tutorial-Part3-Overview.md)
<br>Getting down to the meat of extending Evennia to make our game
- **Part 4: [Using what we created](./Beginner-Tutorial-Part4-Overview.md)**
<br>Building a tech-demo and world content to go with our code
- Part 5: [Showing the world](../Part5/Beginner-Tutorial-Part5-Overview.md)
<br>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.
## Lessons
_TODO_
```{toctree}
:numbered:
:maxdepth: 2
../../../Unimplemented.md
```

View file

@ -27,5 +27,4 @@ _TODO_
:numbered:
:maxdepth: 2
Add-a-simple-new-web-page.md
Web-Tutorial.md
```

View file

@ -31,7 +31,7 @@ instructions. Initialize a new game directory with `evennia init
<gamedirname>`. In this tutorial we assume your game dir is simply named `mygame`. You can use the
default database and keep all other settings to default for now. Familiarize yourself with the
`mygame` folder before continuing. You might want to browse the
[First Steps Coding](Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Intro.md) tutorial, just to see roughly where things are modified.
[First Steps Coding](Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Overview.md) tutorial, just to see roughly where things are modified.
## The Game Master role

View file

@ -1,11 +1,8 @@
# Gametime Tutorial
# Changing game calendar and time speed
A lot of games use a separate time system we refer to as *game time*. This runs in parallel to what
we usually think of as *real time*. The game time might run at a different speed, use different
names for its time units or might even use a completely custom calendar. You don't need to rely on a
game time system at all. But if you do, Evennia offers basic tools to handle these various
situations. This tutorial will walk you through these features.
A lot of games use a separate time system we refer to as *game time*. This runs in parallel to what we usually think of as *real time*. The game time might run at a different speed, use different
names for its time units or might even use a completely custom calendar. You don't need to rely on a game time system at all. But if you do, Evennia offers basic tools to handle these various situations. This tutorial will walk you through these features.
## A game time with a standard calendar
@ -18,8 +15,7 @@ in-game perceived one is easy.
- The intricacies of the real world calendar, with leap years and months of different length etc are
automatically handled by the system.
Evennia's game time features assume a standard calendar (see the relevant section below for a custom
calendar).
Evennia's game time features assume a standard calendar (see the relevant section below for a custom calendar).
### Setting up game time for a standard calendar
@ -39,11 +35,7 @@ TIME_FACTOR = 2.0
TIME_GAME_EPOCH = None
```
By default, the game time runs twice as fast as the real time. You can set the time factor to be 1
(the game time would run exactly at the same speed than the real time) or lower (the game time will
be slower than the real time). Most games choose to have the game time spinning faster (you will
find some games that have a time factor of 60, meaning the game time runs sixty times as fast as the
real time, a minute in real time would be an hour in game time).
By default, the game time runs twice as fast as the real time. You can set the time factor to be 1 (the game time would run exactly at the same speed than the real time) or lower (the game time will be slower than the real time). Most games choose to have the game time spinning faster (you will find some games that have a time factor of 60, meaning the game time runs sixty times as fast as the real time, a minute in real time would be an hour in game time).
The epoch is a slightly more complex setting. It should contain a number of seconds that would
indicate the time your game started. As indicated, an epoch of 0 would mean January 1st, 1970. If
@ -59,8 +51,7 @@ start = datetime(2020, 1, 1)
time.mktime(start.timetuple())
```
This should return a huge number - the number of seconds since Jan 1 1970. Copy that directly into
your settings (editing `server/conf/settings.py`):
This should return a huge number - the number of seconds since Jan 1 1970. Copy that directly into your settings (editing `server/conf/settings.py`):
```python
# in a file settings.py in mygame/server/conf
@ -93,22 +84,15 @@ time updated correctly... and going (by default) twice as fast as the real time.
### Time-related events
The `gametime` utility also has a way to schedule game-related events, taking into account your game
time, and assuming a standard calendar (see below for the same feature with a custom calendar). For
instance, it can be used to have a specific message every (in-game) day at 6:00 AM showing how the
sun rises.
The `gametime` utility also has a way to schedule game-related events, taking into account your game time, and assuming a standard calendar (see below for the same feature with a custom calendar). For instance, it can be used to have a specific message every (in-game) day at 6:00 AM showing how the sun rises.
The function `schedule()` should be used here. It will create a [script](../Components/Scripts.md) with some
additional features to make sure the script is always executed when the game time matches the given
parameters.
The function `schedule()` should be used here. It will create a [script](../Components/Scripts.md) with some additional features to make sure the script is always executed when the game time matches the given parameters.
The `schedule` function takes the following arguments:
- The *callback*, a function to be called when time is up.
- The keyword `repeat` (`False` by default) to indicate whether this function should be called
repeatedly.
- Additional keyword arguments `sec`, `min`, `hour`, `day`, `month` and `year` to describe the time
to schedule. If the parameter isn't given, it assumes the current time value of this specific unit.
- The keyword `repeat` (`False` by default) to indicate whether this function should be called repeatedly.
- Additional keyword arguments `sec`, `min`, `hour`, `day`, `month` and `year` to describe the time to schedule. If the parameter isn't given, it assumes the current time value of this specific unit.
Here is a short example for making the sun rise every day:
@ -140,9 +124,7 @@ The script will be created silently. The `at_sunrise` function will now be calle
at 6 AM. You can use the `@scripts` command to see it. You could stop it using `@scripts/stop`. If
we hadn't set `repeat` the sun would only have risen once and then never again.
We used the `@py` command here: nothing prevents you from adding the system into your game code.
Remember to be careful not to add each event at startup, however, otherwise there will be a lot of
overlapping events scheduled when the sun rises.
We used the `@py` command here: nothing prevents you from adding the system into your game code. Remember to be careful not to add each event at startup, however, otherwise there will be a lot of overlapping events scheduled when the sun rises.
The `schedule` function when using `repeat` set to `True` works with the higher, non-specified unit.
In our example, we have specified hour, minute and second. The higher unit we haven't specified is
@ -156,26 +138,16 @@ such day in February, April etc. Similarly, leap years may change the number of
### A game time with a custom calendar
Using a custom calendar to handle game time is sometimes needed if you want to place your game in a
fictional universe. For instance you may want to create the Shire calendar which Tolkien described
having 12 months, each which 30 days. That would give only 360 days per year (presumably hobbits
weren't really fond of the hassle of following the astronomical calendar). Another example would be
creating a planet in a different solar system with, say, days 29 hours long and months of only 18
days.
Using a custom calendar to handle game time is sometimes needed if you want to place your game in a fictional universe. For instance you may want to create the Shire calendar which Tolkien described having 12 months, each which 30 days. That would give only 360 days per year (presumably hobbits weren't really fond of the hassle of following the astronomical calendar). Another example would be creating a planet in a different solar system with, say, days 29 hours long and months of only 18 days.
Evennia handles custom calendars through an optional *contrib* module, called `custom_gametime`.
Contrary to the normal `gametime` module described above it is not active by default.
### Setting up the custom calendar
In our first example of the Shire calendar, used by hobbits in books by Tolkien, we don't really
need the notion of weeks... but we need the notion of months having 30 days, not 28.
In our first example of the Shire calendar, used by hobbits in books by Tolkien, we don't really need the notion of weeks... but we need the notion of months having 30 days, not 28.
The custom calendar is defined by adding the `TIME_UNITS` setting to your settings file. It's a
dictionary containing as keys the name of the units, and as value the number of seconds (the
smallest unit for us) in this unit. Its keys must be picked among the following: "sec", "min",
"hour", "day", "week", "month" and "year" but you don't have to include them all. Here is the
configuration for the Shire calendar:
The custom calendar is defined by adding the `TIME_UNITS` setting to your settings file. It's a dictionary containing as keys the name of the units, and as value the number of seconds (the smallest unit for us) in this unit. Its keys must be picked among the following: "sec", "min", "hour", "day", "week", "month" and "year" but you don't have to include them all. Here is the configuration for the Shire calendar:
```python
# in a file settings.py in mygame/server/conf
@ -187,12 +159,9 @@ TIME_UNITS = {"sec": 1,
"year": 60 * 60 * 24 * 30 * 12 }
```
We give each unit we want as keys. Values represent the number of seconds in that unit. Hour is
set to 60 * 60 (that is, 3600 seconds per hour). Notice that we don't specify the week unit in this
configuration: instead, we skip from days to months directly.
We give each unit we want as keys. Values represent the number of seconds in that unit. Hour is set to 60 * 60 (that is, 3600 seconds per hour). Notice that we don't specify the week unit in this configuration: instead, we skip from days to months directly.
In order for this setting to work properly, remember all units have to be multiples of the previous
units. If you create "day", it needs to be multiple of hours, for instance.
In order for this setting to work properly, remember all units have to be multiples of the previous units. If you create "day", it needs to be multiple of hours, for instance.
So for our example, our settings may look like this:
@ -215,20 +184,17 @@ TIME_UNITS = {
}
```
Notice we have set a time epoch of 0. Using a custom calendar, we will come up with a nice display
of time on our own. In our case the game time starts at year 0, month 1, day 1, and at midnight.
Notice we have set a time epoch of 0. Using a custom calendar, we will come up with a nice display of time on our own. In our case the game time starts at year 0, month 1, day 1, and at midnight.
> Year, hour, minute and sec starts from 0, month, week and day starts from 1, this makes them
> behave consistently with the standard time.
Note that while we use "month", "week" etc in the settings, your game may not use those terms in-
game, instead referring to them as "cycles", "moons", "sand falls" etc. This is just a matter of you
Note that while we use "month", "week" etc in the settings, your game may not use those terms in- game, instead referring to them as "cycles", "moons", "sand falls" etc. This is just a matter of you
displaying them differently. See next section.
#### A command to display the current game time
As pointed out earlier, the `@time` command is meant to be used with a standard calendar, not a
custom one. We can easily create a new command though. We'll call it `time`, as is often the case
As pointed out earlier, the `@time` command is meant to be used with a standard calendar, not a custom one. We can easily create a new command though. We'll call it `time`, as is often the case
on other MU*. Here's an example of how we could write it (for the example, you can create a file
`gametime.py` in your `commands` directory and paste this code in it):
@ -287,8 +253,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
self.add(CmdTime()) # <- Add
```
Reload your game with the `@reload` command. You should now see the `time` command. If you enter
it, you might see something like:
Reload your game with the `@reload` command. You should now see the `time` command. If you enter it, you might see something like:
We are in year 0, day 0, month 0.
It's 00:52:17.
@ -298,7 +263,4 @@ And if "months" are called "moons" in your game, this is where you'd add that.
## Time-related events in custom gametime
The `custom_gametime` module also has a way to schedule game-related events, taking into account
your game time (and your custom calendar). It can be used to have a specific message every day at
6:00 AM, to show the sun rises, for instance. The `custom_gametime.schedule` function works in the
same way as described for the default one above.
The `custom_gametime` module also has a way to schedule game-related events, taking into account your game time (and your custom calendar). It can be used to have a specific message every day at 6:00 AM, to show the sun rises, for instance. The `custom_gametime.schedule` function works in the same way as described for the default one above.

View file

@ -1,6 +1,8 @@
# Tutorials and Howto's
All Evennia tutorials. They will often refer to the [components](../Components/Components-Overview.md) or [concepts](../Concepts/Concepts-Overview.md) if you want to dive deeper.
```{sidebar} Want more details about something?
See the documentation about the Evennia core [Components](../Components/Components-Overview.md) and important [Concepts](../Concepts/Concepts-Overview.md).
```
## Beginner Tutorial
@ -13,43 +15,30 @@ Part 3 and onwards are still under development.
```{toctree}
:maxdepth: 3
./Beginner-Tutorial/Beginner-Tutorial-Intro
./Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Intro
./Beginner-Tutorial/Part2/Beginner-Tutorial-Part2-Intro
./Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Intro
./Beginner-Tutorial/Part4/Beginner-Tutorial-Part4-Intro
./Beginner-Tutorial/Part5/Beginner-Tutorial-Part5-Intro
./Beginner-Tutorial/Beginner-Tutorial-Overview
./Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Overview
./Beginner-Tutorial/Part2/Beginner-Tutorial-Part2-Overview
./Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Overview
./Beginner-Tutorial/Part4/Beginner-Tutorial-Part4-Overview
./Beginner-Tutorial/Part5/Beginner-Tutorial-Part5-Overview
```
## Howto's
```{toctree}
:maxdepth: 2
:maxdepth: 1
Howto-Command-Prompt.md
Howto-Command-Cooldown.md
Howto-Command-Duration.md
Howto-Default-Exit-Errors.md
Howto-Add-Object-Weight.md
```
## Mobs and NPCs
```{toctree}
:maxdepth: 1
Tutorial-NPC-Listening.md
Tutorial-NPC-Reacting.md
Tutorial-NPC-Merchants.md
```
## Vehicles
```{toctree}
:maxdepth: 1
Building-a-mech-tutorial.md
Tutorial-Vehicles.md
Tutorial-Building-a-Mech.md
Tutorial-Building-a-Train.md
```
## Systems
@ -57,37 +46,38 @@ Tutorial-Vehicles.md
:maxdepth: 1
Tutorial-Persistent-Handler.md
Gametime-Tutorial.md
Weather-Tutorial.md
Howto-Game-Time.md
Tutorial-Weather-Effects.md
Tutorial-Coordinates.md
Dynamic-In-Game-Map.md
Static-In-Game-Map.md
Tutorial-Tweeting-Game-Stats.md
Tutorial-Displaying-Room-Map.md
```
## Web-related tutorials
_Some of these will likely move into the Beginner tutorial later_.
```{toctree}
:maxdepth: 1
Web-Changing-Webpage.md
Web-Add-a-wiki.md
Web-Character-Generation.md
Web-Character-View-Tutorial.md
Web-Help-System-Tutorial.md
Web-Extending-the-REST-API
Web-Tweeting-Game-Stats.md
```
## Deep-dives
```{toctree}
:maxdepth: 1
Parsing-commands-tutorial.md
Tutorial-Parsing-Commands.md
Tutorial-Understanding-Color-Tags.md
Evennia-for-roleplaying-sessions.md
Evennia-for-Diku-Users.md
Evennia-for-MUSH-Users.md
Arxcode-Installation.md
Tutorial-Using-Arxcode.md
```
## Old tutorials

View file

@ -1,416 +0,0 @@
# Static In Game Map
## Introduction
This tutorial describes the creation of an in-game map display based on a pre-drawn map. It also
details how to use the [Batch code processor](../Components/Batch-Code-Processor.md) for advanced building. There is
also the [Dynamic in-game map tutorial](./Dynamic-In-Game-Map.md) that works in the opposite direction,
by generating a map from an existing grid of rooms.
Evennia does not require its rooms to be positioned in a "logical" way. Your exits could be named
anything. You could make an exit "west" that leads to a room described to be in the far north. You
could have rooms inside one another, exits leading back to the same room or describing spatial
geometries impossible in the real world.
That said, most games *do* organize their rooms in a logical fashion, if nothing else to retain the
sanity of their players. And when they do, the game becomes possible to map. This tutorial will give
an example of a simple but flexible in-game map system to further help player's to navigate. We will
To simplify development and error-checking we'll break down the work into bite-size chunks, each
building on what came before. For this we'll make extensive use of the [Batch code processor](Batch-
Code-Processor), so you may want to familiarize yourself with that.
1. **Planning the map** - Here we'll come up with a small example map to use for the rest of the
tutorial.
2. **Making a map object** - This will showcase how to make a static in-game "map" object a
Character could pick up and look at.
3. **Building the map areas** - Here we'll actually create the small example area according to the
map we designed before.
4. **Map code** - This will link the map to the location so our output looks something like this:
```
crossroads(#3)
↑╚∞╝↑
≈↑│↑∩ The merger of two roads. To the north looms a mighty castle.
O─O─O To the south, the glow of a campfire can be seen. To the east lie
≈↑│↑∩ the vast mountains and to the west is heard the waves of the sea.
↑▲O▲↑
Exits: north(#8), east(#9), south(#10), west(#11)
```
We will henceforth assume your game folder is name named `mygame` and that you haven't modified the
default commands. We will also not be using [Colors](../Concepts/Colors.md) for our map since they
don't show in the documentation wiki.
## Planning the Map
Let's begin with the fun part! Maps in MUDs come in many different [shapes and
sizes](http://journal.imaginary-realities.com/volume-05/issue-01/modern-interface-modern-
mud/index.html). Some appear as just boxes connected by lines. Others have complex graphics that are
external to the game itself.
Our map will be in-game text but that doesn't mean we're restricted to the normal alphabet! If
you've ever selected the [Wingdings font](https://en.wikipedia.org/wiki/Wingdings) in Microsoft Word
you will know there are a multitude of other characters around to use. When creating your game with
Evennia you have access to the [UTF-8 character encoding](https://en.wikipedia.org/wiki/UTF-8) which
put at your disposal [thousands of letters, number and geometric shapes](https://mcdlr.com/utf-8/#1).
For this exercise, we've copy-and-pasted from the pallet of special characters used over at
[Dwarf Fortress](https://dwarffortresswiki.org/index.php/Character_table) to create what is hopefully
a pleasing and easy to understood landscape:
```
≈≈↑↑↑↑↑∩∩
≈≈↑╔═╗↑∩∩ Places the account can visit are indicated by "O".
≈≈↑║O║↑∩∩ Up the top is a castle visitable by the account.
≈≈↑╚∞╝↑∩∩ To the right is a cottage and to the left the beach.
≈≈≈↑│↑∩∩∩ And down the bottom is a camp site with tents.
≈≈O─O─O⌂∩ In the center is the starting location, a crossroads
≈≈≈↑│↑∩∩∩ which connect the four other areas.
≈≈↑▲O▲↑∩∩
≈≈↑↑▲↑↑∩∩
≈≈↑↑↑↑↑∩∩
```
There are many considerations when making a game map depending on the play style and requirements
you intend to implement. Here we will display a 5x5 character map of the area surrounding the
account. This means making sure to account for 2 characters around every visitable location. Good
planning at this stage can solve many problems before they happen.
## Creating a Map Object
In this section we will try to create an actual "map" object that an account can pick up and look
at.
Evennia offers a range of [default commands](../Components/Default-Commands.md) for
[creating objects and rooms in-game](Beginner-Tutorial/Part1/Beginner-Tutorial-Building-Quickstart.md). While readily accessible, these commands are made to do very
specific, restricted things and will thus not offer as much flexibility to experiment (for an
advanced exception see [the FuncParser](../Components/FuncParser.md)). Additionally, entering long
descriptions and properties over and over in the game client can become tedious; especially when
testing and you may want to delete and recreate things over and over.
To overcome this, Evennia offers [batch processors](../Components/Batch-Processors.md) that work as input-files
created out-of-game. In this tutorial we'll be using the more powerful of the two available batch
processors, the [Batch Code Processor ](../Components/Batch-Code-Processor.md), called with the `@batchcode` command.
This is a very powerful tool. It allows you to craft Python files to act as blueprints of your
entire game world. These files have access to use Evennia's Python API directly. Batchcode allows
for easy editing and creation in whatever text editor you prefer, avoiding having to manually build
the world line-by-line inside the game.
> Important warning: `@batchcode`'s power is only rivaled by the `@py` command. Batchcode is so
powerful it should be reserved only for the [superuser](../Concepts/Building-Permissions.md). Think carefully
before you let others (such as `Developer`- level staff) run `@batchcode` on their own - make sure
you are okay with them running *arbitrary Python code* on your server.
While a simple example, the map object it serves as good way to try out `@batchcode`. Go to
`mygame/world` and create a new file there named `batchcode_map.py`:
```Python
# mygame/world/batchcode_map.py
from evennia import create_object
from evennia import DefaultObject
# We use the create_object function to call into existence a
# DefaultObject named "Map" wherever you are standing.
map = create_object(DefaultObject, key="Map", location=caller.location)
# We then access its description directly to make it our map.
map.db.desc = """
≈≈↑↑↑↑↑∩∩
≈≈↑╔═╗↑∩∩
≈≈↑║O║↑∩∩
≈≈↑╚∞╝↑∩∩
≈≈≈↑│↑∩∩∩
≈≈O─O─O⌂∩
≈≈≈↑│↑∩∩∩
≈≈↑▲O▲↑∩∩
≈≈↑↑▲↑↑∩∩
≈≈↑↑↑↑↑∩∩
"""
# This message lets us know our map was created successfully.
caller.msg("A map appears out of thin air and falls to the ground.")
```
Log into your game project as the superuser and run the command
```
@batchcode batchcode_map
```
This will load your `batchcode_map.py` file and execute the code (Evennia will look in your `world/`
folder automatically so you don't need to specify it).
A new map object should have appeared on the ground. You can view the map by using `look map`. Let's
take it with the `get map` command. We'll need it in case we get lost!
## Building the map areas
We've just used batchcode to create an object useful for our adventures. But the locations on that
map does not actually exist yet - we're all mapped up with nowhere to go! Let's use batchcode to
build a game area based on our map. We have five areas outlined: a castle, a cottage, a campsite, a
coastal beach and the crossroads which connects them. Create a new batchcode file for this in
`mygame/world`, named `batchcode_world.py`.
```Python
# mygame/world/batchcode_world.py
from evennia import create_object, search_object
from typeclasses import rooms, exits
# We begin by creating our rooms so we can detail them later.
centre = create_object(rooms.Room, key="crossroads")
north = create_object(rooms.Room, key="castle")
east = create_object(rooms.Room, key="cottage")
south = create_object(rooms.Room, key="camp")
west = create_object(rooms.Room, key="coast")
# This is where we set up the cross roads.
# The rooms description is what we see with the 'look' command.
centre.db.desc = """
The merger of two roads. A single lamp post dimly illuminates the lonely crossroads.
To the north looms a mighty castle. To the south the glow of a campfire can be seen.
To the east lie a wall of mountains and to the west the dull roar of the open sea.
"""
# Here we are creating exits from the centre "crossroads" location to
# destinations to the north, east, south, and west. We will be able
# to use the exit by typing it's key e.g. "north" or an alias e.g. "n".
centre_north = create_object(exits.Exit, key="north",
aliases=["n"], location=centre, destination=north)
centre_east = create_object(exits.Exit, key="east",
aliases=["e"], location=centre, destination=east)
centre_south = create_object(exits.Exit, key="south",
aliases=["s"], location=centre, destination=south)
centre_west = create_object(exits.Exit, key="west",
aliases=["w"], location=centre, destination=west)
# Now we repeat this for the other rooms we'll be implementing.
# This is where we set up the northern castle.
north.db.desc = "An impressive castle surrounds you. " \
"There might be a princess in one of these towers."
north_south = create_object(exits.Exit, key="south",
aliases=["s"], location=north, destination=centre)
# This is where we set up the eastern cottage.
east.db.desc = "A cosy cottage nestled among mountains " \
"stretching east as far as the eye can see."
east_west = create_object(exits.Exit, key="west",
aliases=["w"], location=east, destination=centre)
# This is where we set up the southern camp.
south.db.desc = "Surrounding a clearing are a number of " \
"tribal tents and at their centre a roaring fire."
south_north = create_object(exits.Exit, key="north",
aliases=["n"], location=south, destination=centre)
# This is where we set up the western coast.
west.db.desc = "The dark forest halts to a sandy beach. " \
"The sound of crashing waves calms the soul."
west_east = create_object(exits.Exit, key="east",
aliases=["e"], location=west, destination=centre)
# Lastly, lets make an entrance to our world from the default Limbo room.
limbo = search_object('Limbo')[0]
limbo_exit = create_object(exits.Exit, key="enter world",
aliases=["enter"], location=limbo, destination=centre)
```
Apply this new batch code with `@batchcode batchcode_world`. If there are no errors in the code we
now have a nice mini-world to explore. Remember that if you get lost you can look at the map we
created!
## In-game minimap
Now we have a landscape and matching map, but what we really want is a mini-map that displays
whenever we move to a room or use the `look` command.
We *could* manually enter a part of the map into the description of every room like we did our map
object description. But some MUDs have tens of thousands of rooms! Besides, if we ever changed our
map we would have to potentially alter a lot of those room descriptions manually to match the
change. So instead we will make one central module to hold our map. Rooms will reference this
central location on creation and the map changes will thus come into effect when next running our
batchcode.
To make our mini-map we need to be able to cut our full map into parts. To do this we need to put it
in a format which allows us to do that easily. Luckily, python allows us to treat strings as lists
of characters allowing us to pick out the characters we need.
`mygame/world/map_module.py`
```Python
# We place our map into a sting here.
world_map = """\
≈≈↑↑↑↑↑∩∩
≈≈↑╔═╗↑∩∩
≈≈↑║O║↑∩∩
≈≈↑╚∞╝↑∩∩
≈≈≈↑│↑∩∩∩
≈≈O─O─O⌂∩
≈≈≈↑│↑∩∩∩
≈≈↑▲O▲↑∩∩
≈≈↑↑▲↑↑∩∩
≈≈↑↑↑↑↑∩∩
"""
# This turns our map string into a list of rows. Because python
# allows us to treat strings as a list of characters, we can access
# those characters with world_map[5][5] where world_map[row][column].
world_map = world_map.split('\n')
def return_map():
"""
This function returns the whole map
"""
map = ""
#For each row in our map, add it to map
for valuey in world_map:
map += valuey
map += "\n"
return map
def return_minimap(x, y, radius = 2):
"""
This function returns only part of the map.
Returning all chars in a 2 char radius from (x,y)
"""
map = ""
#For each row we need, add the characters we need.
for valuey in world_map[y-radius:y+radius+1]: for valuex in valuey[x-radius:x+radius+1]:
map += valuex
map += "\n"
return map
```
With our map_module set up, let's replace our hardcoded map in `mygame/world/batchcode_map.py` with
a reference to our map module. Make sure to import our map_module!
```python
# mygame/world/batchcode_map.py
from evennia import create_object
from evennia import DefaultObject
from world import map_module
map = create_object(DefaultObject, key="Map", location=caller.location)
map.db.desc = map_module.return_map()
caller.msg("A map appears out of thin air and falls to the ground.")
```
Log into Evennia as the superuser and run this batchcode. If everything worked our new map should
look exactly the same as the old map - you can use `@delete` to delete the old one (use a number to
pick which to delete).
Now, lets turn our attention towards our game's rooms. Let's use the `return_minimap` method we
created above in order to include a minimap in our room descriptions. This is a little more
complicated.
By itself we would have to settle for either the map being *above* the description with
`room.db.desc = map_string + description_string`, or the map going *below* by reversing their order.
Both options are rather unsatisfactory - we would like to have the map next to the text! For this
solution we'll explore the utilities that ship with Evennia. Tucked away in `evennia\evennia\utils`
is a little module called [EvTable](github:evennia.utils.evtable) . This is an advanced ASCII table
creator for you to utilize in your game. We'll use it by creating a basic table with 1 row and two
columns (one for our map and one for our text) whilst also hiding the borders. Open the batchfile
again
```python
# mygame\world\batchcode_world.py
# Add to imports
from evennia.utils import evtable
from world import map_module
# [...]
# Replace the descriptions with the below code.
# The cross roads.
# We pass what we want in our table and EvTable does the rest.
# Passing two arguments will create two columns but we could add more.
# We also specify no border.
centre.db.desc = evtable.EvTable(map_module.return_minimap(4,5),
"The merger of two roads. A single lamp post dimly " \
"illuminates the lonely crossroads. To the north " \
"looms a mighty castle. To the south the glow of " \
"a campfire can be seen. To the east lie a wall of " \
"mountains and to the west the dull roar of the open sea.",
border=None)
# EvTable allows formatting individual columns and cells. We use that here
# to set a maximum width for our description, but letting the map fill
# whatever space it needs.
centre.db.desc.reformat_column(1, width=70)
# [...]
# The northern castle.
north.db.desc = evtable.EvTable(map_module.return_minimap(4,2),
"An impressive castle surrounds you. There might be " \
"a princess in one of these towers.",
border=None)
north.db.desc.reformat_column(1, width=70)
# [...]
# The eastern cottage.
east.db.desc = evtable.EvTable(map_module.return_minimap(6,5),
"A cosy cottage nestled among mountains stretching " \
"east as far as the eye can see.",
border=None)
east.db.desc.reformat_column(1, width=70)
# [...]
# The southern camp.
south.db.desc = evtable.EvTable(map_module.return_minimap(4,7),
"Surrounding a clearing are a number of tribal tents " \
"and at their centre a roaring fire.",
border=None)
south.db.desc.reformat_column(1, width=70)
# [...]
# The western coast.
west.db.desc = evtable.EvTable(map_module.return_minimap(2,5),
"The dark forest halts to a sandy beach. The sound of " \
"crashing waves calms the soul.",
border=None)
west.db.desc.reformat_column(1, width=70)
```
Before we run our new batchcode, if you are anything like me you would have something like 100 maps
lying around and 3-4 different versions of our rooms extending from limbo. Let's wipe it all and
start with a clean slate. In Command Prompt you can run `evennia flush` to clear the database and
start anew. It won't reset dbref values however, so if you are at #100 it will start from there.
Alternatively you can navigate to `mygame/server` and delete the `evennia.db3` file. Now in Command
Prompt use `evennia migrate` to have a completely freshly made database.
Log in to evennia and run `@batchcode batchcode_world` and you'll have a little world to explore.
## Conclusions
You should now have a mapped little world and a basic understanding of batchcode, EvTable and how
easily new game defining features can be added to Evennia.
You can easily build from this tutorial by expanding the map and creating more rooms to explore. Why
not add more features to your game by trying other tutorials: [Add weather to your world](Weather-
Tutorial), [fill your world with NPC's](./Tutorial-NPC-Reacting.md) or
[implement a combat system](./Turn-based-Combat-System.md).

View file

@ -1,67 +1,49 @@
# Building a mech tutorial
# Building a giant mech
> This page was adapted from the article "Building a Giant Mech in Evennia" by Griatch, published in
Imaginary Realities Volume 6, issue 1, 2014. The original article is no longer available online,
this is a version adopted to be compatible with the latest Evennia.
Let us create a functioning giant mech in Evennia. Everyone likes a giant mech, right? Start in-game as a character with build privileges (or the superuser).
## Creating the Mech
Let us create a functioning giant mech using the Python MUD-creation system Evennia. Everyone likes
a giant mech, right? Start in-game as a character with build privileges (or the superuser).
@create/drop Giant Mech ; mech
create/drop Giant Mech ; mech
Boom. We created a Giant Mech Object and dropped it in the room. We also gave it an alias *mech*.
Lets describe it.
@desc mech = This is a huge mech. It has missiles and stuff.
desc mech = This is a huge mech. It has missiles and stuff.
Next we define who can “puppet” the mech object.
@lock mech = puppet:all()
lock mech = puppet:all()
This makes it so that everyone can control the mech. More mechs to the people! (Note that whereas
Evennias default commands may look vaguely MUX-like, you can change the syntax to look like
whatever interface style you prefer.)
This makes it so that everyone can control the mech. More mechs to the people! (Note that whereas Evennias default commands may look vaguely MUX-like, you can change the syntax to look like whatever interface style you prefer.)
Before we continue, lets make a brief detour. Evennia is very flexible about its objects and even
more flexible about using and adding commands to those objects. Here are some ground rules well
worth remembering for the remainder of this article:
Before we continue, lets make a brief detour. Evennia is very flexible about its objects and even more flexible about using and adding commands to those objects. Here are some ground rules well worth remembering for the remainder of this article:
- The [Account](../Components/Accounts.md) represents the real person logging in and has no game-world existence.
- Any [Object](../Components/Objects.md) can be puppeted by an Account (with proper permissions).
- [Characters](../Components/Objects.md#characters), [Rooms](../Components/Objects.md#rooms), and [Exits](../Components/Objects.md#exits) are just
children of normal Objects.
- [Characters](../Components/Objects.md#characters), [Rooms](../Components/Objects.md#rooms), and [Exits](../Components/Objects.md#exits) are just children of normal Objects.
- Any Object can be inside another (except if it creates a loop).
- Any Object can store custom sets of commands on it. Those commands can:
- be made available to the puppeteer (Account),
- be made available to anyone in the same location as the Object, and
- be made available to anyone “inside” the Object
- Also Accounts can store commands on themselves. Account commands are always available unless
commands on a puppeted Object explicitly override them.
- Also Accounts can store commands on themselves. Account commands are always available unless commands on a puppeted Object explicitly override them.
In Evennia, using the `@ic` command will allow you to puppet a given Object (assuming you have
puppet-access to do so). As mentioned above, the bog-standard Character class is in fact like any
Object: it is auto-puppeted when logging in and just has a command set on it containing the normal
in-game commands, like look, inventory, get and so on.
In Evennia, using the `ic` command will allow you to puppet a given Object (assuming you have puppet-access to do so). As mentioned above, the bog-standard Character class is in fact like any Object: it is auto-puppeted when logging in and just has a command set on it containing the normal in-game commands, like look, inventory, get and so on.
@ic mech
ic mech
You just jumped out of your Character and *are* now the mech! If people look at you in-game, they
will look at a mech. The problem at this point is that the mech Object has no commands of its own.
The usual things like look, inventory and get sat on the Character object, remember? So at the
moment the mech is not quite as cool as it could be.
@ic <Your old Character>
ic <Your old Character>
You just jumped back to puppeting your normal, mundane Character again. All is well.
> (But, you ask, where did that `@ic` command come from, if the mech had no commands on it? The
answer is that it came from the Account's command set. This is important. Without the Account being
the one with the `@ic` command, we would not have been able to get back out of our mech again.)
> Where did that `ic` command come from, if the mech had no commands on it? The
answer is that it came from the `Account`'s command set. This is important. Without the Account being the one with the `ic` command, we would not have been able to get back out of our mech again.
### Arming the Mech
## Make a Mech that can shoot
Let us make the mech a little more interesting. In our favorite text editor, we will create some new
mech-suitable commands. In Evennia, commands are defined as Python classes.
@ -109,20 +91,12 @@ class CmdLaunch(Command):
```
This is saved as a normal Python module (lets call it `mechcommands.py`), in a place Evennia looks
for such modules (`mygame/commands/`). This command will trigger when the player gives the command
“shoot”, “fire,” or even “fire!” with an exclamation mark. The mech can shoot in the air or at a
target if you give one. In a real game the gun would probably be given a chance to hit and give
This is saved as a normal Python module (lets call it `mechcommands.py`), in a place Evennia looks for such modules (`mygame/commands/`). This command will trigger when the player gives the command “shoot”, “fire,” or even “fire!” with an exclamation mark. The mech can shoot in the air or at a target if you give one. In a real game the gun would probably be given a chance to hit and give
damage to the target, but this is enough for now.
We also make a second command for launching missiles (`CmdLaunch`). To save
space we wont describe it here; it looks the same except it returns a text
about the missiles being fired and has different `key` and `aliases`. We leave
that up to you to create as an exercise. You could have it print "WOOSH! The
mech launches missiles against <target>!", for example.
We also make a second command for launching missiles (`CmdLaunch`). To save space we wont describe it here; it looks the same except it returns a text about the missiles being fired and has different `key` and `aliases`. We leave that up to you to create as an exercise. You could have it print `"WOOSH! The mech launches missiles against <target>!`, for example.
Now we shove our commands into a command set. A [Command Set](../Components/Command-Sets.md) (CmdSet) is a container
holding any number of commands. The command set is what we will store on the mech.
Now we shove our commands into a command set. A [Command Set](../Components/Command-Sets.md) (CmdSet) is a container holding any number of commands. The command set is what we will store on the mech.
```python
# in the same file mygame/commands/mechcommands.py
@ -142,37 +116,29 @@ class MechCmdSet(CmdSet):
self.add(CmdLaunch())
```
This simply groups all the commands we want. We add our new shoot/launch commands. Lets head back
into the game. For testing we will manually attach our new CmdSet to the mech.
This simply groups all the commands we want. We add our new shoot/launch commands. Lets head back into the game. For testing we will manually attach our new CmdSet to the mech.
@py self.search("mech").cmdset.add("commands.mechcommands.MechCmdSet")
py self.search("mech").cmdset.add("commands.mechcommands.MechCmdSet")
This is a little Python snippet (run from the command line as an admin) that searches for the mech
in our current location and attaches our new MechCmdSet to it. What we add is actually the Python
path to our cmdset class. Evennia will import and initialize it behind the scenes.
This is a little Python snippet that searches for the mech in our current location and attaches our new MechCmdSet to it. What we add is actually the Python path to our cmdset class. Evennia will import and initialize it behind the scenes.
@ic mech
ic mech
We are back as the mech! Lets do some shooting!
fire!
BOOM! The mech fires its gun in the air!
There we go, one functioning mech. Try your own `launch` command and see that it works too. We can
not only walk around as the mech — since the CharacterCmdSet is included in our MechCmdSet, the mech
can also do everything a Character could do, like look around, pick up stuff, and have an inventory.
We could now shoot the gun at a target or try the missile launch command. Once you have your own
mech, what else do you need?
There we go, one functioning mech. Try your own `launch` command and see that it works too. We can not only walk around as the mech — since the CharacterCmdSet is included in our MechCmdSet, the mech can also do everything a Character could do, like look around, pick up stuff, and have an inventory. We could now shoot the gun at a target or try the missile launch command. Once you have your own mech, what else do you need?
> Note: You'll find that the mech's commands are available to you by just standing in the same
> You'll find that the mech's commands are available to you by just standing in the same
location (not just by puppeting it). We'll solve this with a *lock* in the next section.
## Making a Mech production line
## Making an army of Mechs
What weve done so far is just to make a normal Object, describe it and put some commands on it.
This is great for testing. The way we added it, the MechCmdSet will even go away if we reload the
server. Now we want to make the mech an actual object “type” so we can create mechs without those
extra steps. For this we need to create a new Typeclass.
server. Now we want to make the mech an actual object “type” so we can create mechs without those extra steps. For this we need to create a new Typeclass.
A [Typeclass](../Components/Typeclasses.md) is a near-normal Python class that stores its existence to the database
behind the scenes. A Typeclass is created in a normal Python source file:
@ -196,42 +162,31 @@ class Mech(Object):
self.db.desc = "This is a huge mech. It has missiles and stuff."
```
For convenience we include the full contents of the default `CharacterCmdSet` in there. This will
make a Characters normal commands available to the mech. We also add the mech-commands from before,
making sure they are stored persistently in the database. The locks specify that anyone can puppet
the meck and no-one can "call" the mech's Commands from 'outside' it - you have to puppet it to be
able to shoot.
For convenience we include the full contents of the default `CharacterCmdSet` in there. This will make a Characters normal commands available to the mech. We also add the mech-commands from before, making sure they are stored persistently in the database. The locks specify that anyone can puppet the meck and no-one can "call" the mech's Commands from 'outside' it - you have to puppet it to be able to shoot.
Thats it. When Objects of this type are created, they will always start out with the mechs command
set and the correct lock. We set a default description, but you would probably change this with
`@desc` to individualize your mechs as you build them.
Thats it. When Objects of this type are created, they will always start out with the mechs command set and the correct lock. We set a default description, but you would probably change this with `desc` to individualize your mechs as you build them.
Back in the game, just exit the old mech (`@ic` back to your old character) then do
@create/drop The Bigger Mech ; bigmech : mech.Mech
create/drop The Bigger Mech ; bigmech : mech.Mech
We create a new, bigger mech with an alias bigmech. Note how we give the python-path to our
Typeclass at the end — this tells Evennia to create the new object based on that class (we don't
have to give the full path in our game dir `typeclasses.mech.Mech` because Evennia knows to look in
the `typeclasses` folder already). A shining new mech will appear in the room! Just use
have to give the full path in our game dir `typeclasses.mech.Mech` because Evennia knows to look in the `typeclasses` folder already). A shining new mech will appear in the room! Just use
@ic bigmech
ic bigmech
to take it on a test drive.
## Future Mechs
### Future Mechs
To expand on this you could add more commands to the mech and remove others. Maybe the mech
shouldnt work just like a Character after all. Maybe it makes loud noises every time it passes from
room to room. Maybe it cannot pick up things without crushing them. Maybe it needs fuel, ammo and
repairs. Maybe youll lock it down so it can only be puppeted by emo teenagers.
Having you puppet the mech-object directly is also just one way to implement a giant mech in
Evennia.
Having you puppet the mech-object directly is just one way to implement a giant mech in Evennia.
For example, you could instead picture a mech as a “vehicle” that you “enter” as your normal
Character (since any Object can move inside another). In that case the “insides” of the mech Object
could be the “cockpit”. The cockpit would have the `MechCommandSet` stored on itself and all the
shooting goodness would be made available to you only when you enter it.
Character (since any Object can move inside another). In that case the “insides” of the mech Object could be the “cockpit”. The cockpit would have the `MechCommandSet` stored on itself and all the shooting goodness would be made available to you only when you enter it.
And of course you could put more guns on it. And make it fly.
To expand on this you could add more commands to the mech and remove others. Maybe the mech shouldnt work just like a Character after all.
Maybe it makes loud noises every time it passes from room to room. Maybe it cannot pick up things without crushing them. Maybe it needs fuel, ammo and repairs. Maybe youll lock it down so it can only be puppeted by emo teenagers.
And of course you could put more guns on it. And make it fly.

View file

@ -1,15 +1,11 @@
# Tutorial Vehicles
# Building a train that moves
> TODO: This should be updated for latest Evennia use.
This tutorial explains how you can create vehicles that can move around in your world. The tutorial
will explain how to create a train, but this can be equally applied to create other kind of vehicles
Vehicles are things that you can enter and then move around in your game world. Here we'll explain how to create a train, but this can be equally applied to create other kind of vehicles
(cars, planes, boats, spaceships, submarines, ...).
## How it works
Objects in Evennia have an interesting property: you can put any object inside another object. This
is most obvious in rooms: a room in Evennia is just like any other game object (except rooms tend to
not themselves be inside anything else).
Objects in Evennia have an interesting property: you can put any object inside another object. This is most obvious in rooms: a room in Evennia is just like any other game object (except rooms tend to not themselves be inside anything else).
Our train will be similar: it will be an object that other objects can get inside. We then simply
move the Train, which brings along everyone inside it.
@ -20,7 +16,7 @@ The first step we need to do is create our train object, including a new typecla
create a new file, for instance in `mygame/typeclasses/train.py` with the following content:
```python
# file mygame/typeclasses/train.py
# in mygame/typeclasses/train.py
from evennia import DefaultObject
@ -35,28 +31,24 @@ class TrainObject(DefaultObject):
Now we can create our train in our game:
```
@create/drop train:train.TrainObject
create/drop train:train.TrainObject
```
Now this is just an object that doesn't do much yet... but we can already force our way inside it
and back (assuming we created it in limbo).
```
@tel train
@tel limbo
tel train
tel limbo
```
## Entering and leaving the train
Using the `@tel`command like shown above is obviously not what we want. `@tel` is an admin command
and normal players will thus never be able to enter the train! It is also not really a good idea to
use [Exits](../Components/Objects.md#exits) to get in and out of the train - Exits are (at least by default) objects
too. They point to a specific destination. If we put an Exit in this room leading inside the train
it would stay here when the train moved away (still leading into the train like a magic portal!). In
the same way, if we put an Exit object inside the train, it would always point back to this room,
regardless of where the Train has moved. Now, one *could* define custom Exit types that move with
the train or change their destination in the right way - but this seems to be a pretty cumbersome
solution.
Using the `tel`command like shown above is obviously not what we want. `@tel` is an admin command and normal players will thus never be able to enter the train!
It is also not really a good idea to use [Exits](../Components/Objects.md#exits) to get in and out of the train - Exits are (at least by default) objects too. They point to a specific destination. If we put an Exit in this room leading inside the train it would stay here when the train moved away (still leading into the train like a magic portal!). In the same way, if we put an Exit object inside the train, it would always point back to this room, regardless of where the Train has moved.
Now, one *could* define custom Exit types that move with the train or change their destination in the right way - but this seems to be a pretty cumbersome solution.
What we will do instead is to create some new [commands](../Components/Commands.md): one for entering the train and
another for leaving it again. These will be stored *on the train object* and will thus be made
@ -81,7 +73,6 @@ class CmdEnterTrain(Command):
"""
key = "enter train"
locks = "cmd:all()"
def func(self):
train = self.obj
@ -102,7 +93,6 @@ class CmdLeaveTrain(Command):
"""
key = "leave train"
locks = "cmd:all()"
def func(self):
train = self.obj
@ -119,39 +109,34 @@ class CmdSetTrain(CmdSet):
Note that while this seems like a lot of text, the majority of lines here are taken up by
documentation.
These commands are work in a pretty straightforward way: `CmdEnterTrain` moves the location of the
player to inside the train and `CmdLeaveTrain` does the opposite: it moves the player back to the
current location of the train (back outside to its current location). We stacked them in a
[cmdset](../Components/Command-Sets.md) `CmdSetTrain` so they can be used.
These commands are work in a pretty straightforward way: `CmdEnterTrain` moves the location of the player to inside the train and `CmdLeaveTrain` does the opposite: it moves the player back to the
current location of the train (back outside to its current location). We stacked them in a [cmdset](../Components/Command-Sets.md) `CmdSetTrain` so they can be used.
To make the commands work we need to add this cmdset to our train typeclass:
```python
# file mygame/typeclasses/train.py
from evennia import DefaultObject
from commands.train import CmdSetTrain
from typeclasses.objects import Object
class TrainObject(DefaultObject):
class TrainObject(Object):
def at_object_creation(self):
self.cmdset.add_default(CmdSetTrain)
```
If we now `@reload` our game and reset our train, those commands should work and we can now enter
and leave the train:
If we now `reload` our game and reset our train, those commands should work and we can now enter and leave the train:
```
@reload
@typeclass/force/reset train = train.TrainObject
reload
typeclass/force/reset train = train.TrainObject
enter train
leave train
```
Note the switches used with the `@typeclass` command: The `/force` switch is necessary to assign our
object the same typeclass we already have. The `/reset` re-triggers the typeclass'
`at_object_creation()` hook (which is otherwise only called the very first an instance is created).
Note the switches used with the `typeclass` command: The `/force` switch is necessary to assign our object the same typeclass we already have. The `/reset` re-triggers the typeclass' `at_object_creation()` hook (which is otherwise only called the very first an instance is created).
As seen above, when this hook is called on our train, our new cmdset will be loaded.
## Locking down the commands
@ -159,17 +144,12 @@ As seen above, when this hook is called on our train, our new cmdset will be loa
If you have played around a bit, you've probably figured out that you can use `leave train` when
outside the train and `enter train` when inside. This doesn't make any sense ... so let's go ahead
and fix that. We need to tell Evennia that you can not enter the train when you're already inside
or leave the train when you're outside. One solution to this is [locks](../Components/Locks.md): we will lock down
the commands so that they can only be called if the player is at the correct location.
or leave the train when you're outside. One solution to this is [locks](../Components/Locks.md): we will lock down the commands so that they can only be called if the player is at the correct location.
Right now commands defaults to the lock `cmd:all()`. The `cmd` lock type in combination with the
`all()` lock function means that everyone can run those commands as long as they are in the same
room as the train *or* inside the train. We're going to change this to check the location of the
player and *only* allow access if they are inside the train.
Since we didn't set a `lock` property on the Command, it defaults to `cmd:all()`. This means that everyone can use the command as long as they are in the same room _or inside the train_.
First of all we need to create a new lock function. Evennia comes with many lock functions built-in
already, but none that we can use for locking a command in this particular case. Create a new entry
in `mygame/server/conf/lockfuncs.py`:
already, but none that we can use for locking a command in this particular case. Create a new entry in `mygame/server/conf/lockfuncs.py`:
```python
@ -187,12 +167,7 @@ def cmdinside(accessing_obj, accessed_obj, *args, **kwargs):
If you didn't know, Evennia is by default set up to use all functions in this module as lock
functions (there is a setting variable that points to it).
Our new lock function, `cmdinside`, is to be used by Commands. The `accessed_obj` is the Command
object (in our case this will be `CmdEnterTrain` and `CmdLeaveTrain`) — Every command has an `obj`
property: this is the the object on which the command "sits". Since we added those commands to our
train object, the `.obj` property will be set to the train object. Conversely, `accessing_obj` is
the object that called the command: in our case it's the Character trying to enter or leave the
train.
Our new lock function, `cmdinside`, is to be used by Commands. The `accessed_obj` is the Command object (in our case this will be `CmdEnterTrain` and `CmdLeaveTrain`) — Every command has an `obj` property: this is the the object on which the command "sits". Since we added those commands to our train object, the `.obj` property will be set to the train object. Conversely, `accessing_obj` is the object that called the command: in our case it's the Character trying to enter or leave the train.
What this function does is to check that the player's location is the same as the train object. If
it is, it means the player is inside the train. Otherwise it means the player is somewhere else and
@ -226,33 +201,28 @@ user ignores lock functions. In order to use this functionality you need to `@qu
Now that we can enter and leave the train correctly, it's time to make it move. There are different
things we need to consider for this:
* Who can control your vehicle? The first player to enter it, only players that have a certain
"drive" skill, automatically?
* Where should it go? Can the player steer the vehicle to go somewhere else or will it always follow
the same route?
* Who can control your vehicle? The first player to enter it, only players that have a certain "drive" skill, automatically?
* Where should it go? Can the player steer the vehicle to go somewhere else or will it always follow the same route?
For our example train we're going to go with automatic movement through a predefined route (its
track). The train will stop for a bit at the start and end of the route to allow players to enter
and leave it.
For our example train we're going to go with automatic movement through a predefined route (its track). The train will stop for a bit at the start and end of the route to allow players to enter and leave it.
Go ahead and create some rooms for our train. Make a list of the room ids along the route (using the
`@ex` command).
Go ahead and create some rooms for our train. Make a list of the room ids along the route (using the `xe` command).
```
@dig/tel South station
@ex # note the id of the station
@tunnel/tel n = Following a railroad
@ex # note the id of the track
@tunnel/tel n = Following a railroad
...
@tunnel/tel n = North Station
> dig/tel South station
> ex # note the id of the station
> tunnel/tel n = Following a railroad
> ex # note the id of the track
> tunnel/tel n = Following a railroad
> ...
> tunnel/tel n = North Station
```
Put the train onto the tracks:
```
@tel south station
@tel train = here
tel south station
tel train = here
```
Next we will tell the train how to move and which route to take.
@ -296,31 +266,27 @@ class TrainObject(DefaultObject):
self.msg_contents(f"The train is moving forward to {room.name}.")
```
We added a lot of code here. Since we changed the `at_object_creation` to add in variables we will
have to reset our train object like earlier (using the `@typeclass/force/reset` command).
We added a lot of code here. Since we changed the `at_object_creation` to add in variables we will have to reset our train object like earlier (using the `@typeclass/force/reset` command).
We are keeping track of a few different things now: whether the train is moving or standing still,
which direction the train is heading to and what rooms the train will pass through.
We also added some methods: one to start moving the train, another to stop and a third that actually
moves the train to the next room in the list. Or makes it stop driving if it reaches the last stop.
We also added some methods: one to start moving the train, another to stop and a third that actually moves the train to the next room in the list. Or makes it stop driving if it reaches the last stop.
Let's try it out, using `@py` to call the new train functionality:
Let's try it out, using `py` to call the new train functionality:
```
@reload
@typeclass/force/reset train = train.TrainObject
enter train
@py here.goto_next_room()
> reload
> typeclass/force/reset train = train.TrainObject
> enter train
> py here.goto_next_room()
```
You should see the train moving forward one step along the rail road.
## Adding in scripts
If we wanted full control of the train we could now just add a command to step it along the track
when desired. We want the train to move on its own though, without us having to force it by manually
calling the `goto_next_room` method.
If we wanted full control of the train we could now just add a command to step it along the track when desired. We want the train to move on its own though, without us having to force it by manually calling the `goto_next_room` method.
To do this we will create two [scripts](../Components/Scripts.md): one script that runs when the train has stopped at
a station and is responsible for starting the train again after a while. The other script will take
@ -389,9 +355,9 @@ class TrainObject(DefaultObject):
```
```
@reload
@typeclass/force/reset train = train.TrainObject
enter train
> reload
> typeclass/force/reset train = train.TrainObject
> enter train
# output:
< The train is moving forward to Following a railroad.
@ -411,12 +377,8 @@ Our train will stop 30 seconds at each end station and then turn around to go ba
This train is very basic and still has some flaws. Some more things to do:
* Make it look like a train.
* Make it impossible to exit and enter the train mid-ride. This could be made by having the
enter/exit commands check so the train is not moving before allowing the caller to proceed.
* Make it impossible to exit and enter the train mid-ride. This could be made by having the enter/exit commands check so the train is not moving before allowing the caller to proceed.
* Have train conductor commands that can override the automatic start/stop.
* Allow for in-between stops between the start- and end station
* Have a rail road track instead of hard-coding the rooms in the train object. This could for
example be a custom [Exit](../Components/Objects.md#exits) only traversable by trains. The train will follow the
track. Some track segments can split to lead to two different rooms and a player can switch the
direction to which room it goes.
* Have a rail road track instead of hard-coding the rooms in the train object. This could for example be a custom [Exit](../Components/Objects.md#exits) only traversable by trains. The train will follow the track. Some track segments can split to lead to two different rooms and a player can switch the direction to which room it goes.
* Create another kind of vehicle!

View file

@ -1,15 +1,11 @@
# Coordinates
# Adding room coordinates to your game
# Adding room coordinates in your game
```{sidebar} The XYZGrid
See also the [XYZGrid contrib](../Contribs/Contrib-XYZGrid.md), which adds coordinate support and pathfinding.
```
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.
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:
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
@ -25,14 +21,9 @@ instance.
The first concept might be the most surprising at first glance: we will create coordinates as
[tags](../Components/Tags.md).
> Why not attributes, wouldn't that be easier?
So, 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.
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
Rather than giving you a step-by-step process, We'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`:
@ -120,11 +111,7 @@ What it does is pretty simple:
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.
So can Tags 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:
@ -143,21 +130,17 @@ Now, let's look at the method that will be called when we wish to set `x` 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
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:
@ -203,24 +186,19 @@ class Room(DefaultRoom):
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:
This solution includes some [Django queries](Basic-Tutorial-Django-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:
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)
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
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
@ -286,9 +264,7 @@ 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.
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
@ -309,11 +285,7 @@ An example might help. Consider this very simple map (a textual description fol
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.
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:
@ -321,13 +293,8 @@ 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.
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:
@ -343,7 +310,7 @@ So in the end we might obtain something like this:
You can try with more examples if you want to see this in action.
### To conclude
## 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.
You can also 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,53 +1,44 @@
# Dynamic In Game Map
# Show a dynamic map of rooms
## Introduction
```{sidebar}
See also the [Mapbuilder](../Contribs/Contrib-Mapbuilder.md) and [XYZGrid](../Contribs/Contrib-XYZGrid.md) contribs, which offer alternative ways of both creating and displaying room maps.
```
An often desired feature in a MUD is to show an in-game map to help navigation.
An often desired feature in a MUD is to show an in-game map to help navigation. The [Static in-game
map](../Contribs/Contrib-Mapbuilder.md) tutorial solves this by creating a *static* map, meaning the map is pre-
drawn once and for all - the rooms are then created to match that map. When walking around, parts of
the static map is then cut out and displayed next to the room description.
```
Forest path
In this tutorial we'll instead do it the other way around; We will dynamically draw the map based on
the relationships we find between already existing rooms.
[.] [.]
[.][.][@][.][.][.]
[.] [.][.][.]
The trees are looming over the narrow forest path.
Exits: East, West
```
## The Grid of Rooms
There are at least two requirements needed for this tutorial to work.
1. The structure of your mud has to follow a logical layout. Evennia supports the layout of your
world to be 'logically' impossible with rooms looping to themselves or exits leading to the other
side of the map. Exits can also be named anything, from "jumping out the window" to "into the fifth
dimension". This tutorial assumes you can only move in the cardinal directions (N, E, S and W).
2. Rooms must be connected and linked together for the map to be generated correctly. Vanilla
Evennia comes with a admin command [tunnel](evennia.commands.default.building.CmdTunnel) that allows a
user to create rooms in the cardinal directions, but additional work is needed to assure that rooms
are connected. For example, if you `tunnel east` and then immediately do `tunnel west` you'll find
that you have created two completely stand-alone rooms. So care is needed if you want to create a
"logical" layout. In this tutorial we assume you have such a grid of rooms that we can generate the
map from.
1. The structure of your mud has to follow a logical layout. Evennia supports the layout of your world to be 'logically' impossible with rooms looping to themselves or exits leading to the other side of the map. Exits can also be named anything, from "jumping out the window" to "into the fifth dimension". This tutorial assumes you can only move in the cardinal directions (N, E, S and W).
2. Rooms must be connected and linked together for the map to be generated correctly. Vanilla Evennia comes with a admin command [tunnel](evennia.commands.default.building.CmdTunnel) that allows a user to create rooms in the cardinal directions, but additional work is needed to assure that rooms are connected. For example, if you `tunnel east` and then immediately do `tunnel west` you'll find that you have created two completely stand-alone rooms. So care is needed if you want to create a "logical" layout. In this tutorial we assume you have such a grid of rooms that we can generate the map from.
## Concept
Before getting into the code, it is beneficial to understand and conceptualize how this is going to
work. The idea is analogous to a worm that starts at your current position. It chooses a direction
and 'walks' outward from it, mapping its route as it goes. Once it has traveled a pre-set distance
it stops and starts over in another direction. An important note is that we want a system which is
easily callable and not too complicated. Therefore we will wrap this entire code into a custom
Python class (not a typeclass as this doesn't use any core objects from evennia itself).
We are going to create something that displays like this when you type 'look':
Before getting into the code, it is beneficial to understand and conceptualize how this is going to work. The idea is analogous to a worm that starts at your current position. It chooses a direction and 'walks' outward from it, mapping its route as it goes. Once it has traveled a pre-set distance it stops and starts over in another direction. An important note is that we want a system which is easily callable and not too complicated. Therefore we will wrap this entire code into a custom Python class (not a typeclass as this doesn't use any core objects from evennia itself). We are going to create something that displays like this when you type 'look':
```
Hallway
Hallway
[.] [.]
[@][.][.][.][.]
[.] [.] [.]
[.] [.]
[@][.][.][.][.]
[.] [.] [.]
The distant echoes of the forgotten
wail throughout the empty halls.
The distant echoes of the forgotten
wail throughout the empty halls.
Exits: North, East, South
Exits: North, East, South
```
Your current location is defined by `[@]` while the `[.]`s are other rooms that the "worm" has seen
@ -55,12 +46,7 @@ since departing from your location.
## Setting up the Map Display
First we must define the components for displaying the map. For the "worm" to know what symbol to
draw on the map we will have it check an Attribute on the room it visits called `sector_type`. For
this tutorial we understand two symbols - a normal room and the room with us in it. We also define a
fallback symbol for rooms without said Attribute - that way the map will still work even if we
didn't prepare the room correctly. Assuming your game folder is named `mygame`, we create this code
in `mygame/world/map.py.`
First we must define the components for displaying the map. For the "worm" to know what symbol to draw on the map we will have it check an Attribute on the room it visits called `sector_type`. For this tutorial we understand two symbols - a normal room and the room with us in it. We also define a fallback symbol for rooms without said Attribute - that way the map will still work even if we didn't prepare the room correctly. Assuming your game folder is named `mygame`, we create this code in `mygame/world/map.py.`
```python
# in mygame/world/map.py
@ -91,18 +77,11 @@ class Map(object):
```
- `self.caller` is normally your Character object, the one using the map.
- `self.max_width/length` determine the max width and length of the map that will be generated. Note
that it's important that these variables are set to *odd* numbers to make sure the display area has
a center point.
- ` self.worm_has_mapped` is building off the worm analogy above. This dictionary will store all
rooms the "worm" has mapped as well as its relative position within the grid. This is the most
important variable as it acts as a 'checker' and 'address book' that is able to tell us where the
worm has been and what it has mapped so far.
- `self.max_width/length` determine the max width and length of the map that will be generated. Note that it's important that these variables are set to *odd* numbers to make sure the display area has a center point.
- ` self.worm_has_mapped` is building off the worm analogy above. This dictionary will store all rooms the "worm" has mapped as well as its relative position within the grid. This is the most important variable as it acts as a 'checker' and 'address book' that is able to tell us where the worm has been and what it has mapped so far.
- `self.curX/Y` are coordinates representing the worm's current location on the grid.
Before any sort of mapping can actually be done we need to create an empty display area and do some
sanity checks on it by using the following methods.
Before any sort of mapping can actually be done we need to create an empty display area and do some sanity checks on it by using the following methods.
```python
# in mygame/world/map.py
@ -127,8 +106,7 @@ class Map(object):
else False
```
Before we can set our worm on its way, we need to know some of the computer science behind all this
called 'Graph Traversing'. In Pseudo code what we are trying to accomplish is this:
Before we can set our worm on its way, we need to know some of the computer science behind all this called 'Graph Traversing'. In Pseudo code what we are trying to accomplish is this:
```python
# pseudo code
@ -151,13 +129,9 @@ def draw_room_on_map(room, max_distance):
The beauty of Python is that our actual code of doing this doesn't differ much if at all from this
Pseudo code example.
- `max_distance` is a variable indicating to our Worm how many rooms AWAY from your current location
will it map. Obviously the larger the number the more time it will take if your current location has
many many rooms around you.
- `max_distance` is a variable indicating to our Worm how many rooms AWAY from your current location will it map. Obviously the larger the number the more time it will take if your current location has many many rooms around you.
The first hurdle here is what value to use for 'max_distance'. There is no reason for the worm to
travel further than what is actually displayed to you. For example, if your current location is
placed in the center of a display area of size `max_length = max_width = 9`, then the worm need only
The first hurdle here is what value to use for 'max_distance'. There is no reason for the worm to travel further than what is actually displayed to you. For example, if your current location is placed in the center of a display area of size `max_length = max_width = 9`, then the worm need only
go `4` spaces in either direction:
```
@ -165,24 +139,17 @@ go `4` spaces in either direction:
4 3 2 1 0 1 2 3 4
```
The `max_distance` can be set dynamically based on the size of the display area. As your width/length
changes it becomes a simple algebraic linear relationship which is simply `max_distance =
(min(max_width, max_length) -1) / 2`.
The `max_distance` can be set dynamically based on the size of the display area. As your width/length changes it becomes a simple algebraic linear relationship which is simply `max_distance = (min(max_width, max_length) -1) / 2`.
## Building the Mapper
Now we can start to fill our Map object with some methods. We are still missing a few methods that
are very important:
Now we can start to fill our Map object with some methods. We are still missing a few methods that are very important:
* `self.draw(self, room)` - responsible for actually drawing room to grid.
* `self.has_drawn(self, room)` - checks to see if the room has been mapped and worm has already been
here.
* `self.median(self, number)` - a simple utility method that finds the median (middle point) from 0,
n
* `self.update_pos(self, room, exit_name)` - updates the worm's physical position by reassigning
self.curX/Y. .accordingly
* `self.start_loc_on_grid(self)` - the very first initial draw on the grid representing your
location in the middle of the grid
* `self.has_drawn(self, room)` - checks to see if the room has been mapped and worm has already been here.
* `self.median(self, number)` - a simple utility method that finds the median (middle point) from `0, n`
* `self.update_pos(self, room, exit_name)` - updates the worm's physical position by reassigning `self.curX/Y` accordingly.
* `self.start_loc_on_grid(self)` - the very first initial draw on the grid representing your location in the middle of the grid.
* `self.show_map` - after everything is done convert the map into a readable string
* `self.draw_room_on_map(self, room, max_distance)` - the main method that ties it all together.
@ -190,7 +157,6 @@ location in the middle of the grid
Now that we know which methods we need, let's refine our initial `__init__(self)` to pass some
conditional statements and set it up to start building the display.
```python
#mygame/world/map.py
@ -213,11 +179,9 @@ class Map(object):
```
Here we check to see if the parameters for the grid are okay, then we create an empty canvas and map
our initial location as the first room!
Here we check to see if the parameters for the grid are okay, then we create an empty canvas and map our initial location as the first room!
As mentioned above, the code for the `self.draw_room_on_map()` is not much different than the Pseudo
code. The method is shown below:
As mentioned above, the code for the `self.draw_room_on_map()` is not much different than the Pseudo code. The method is shown below:
```python
# in mygame/world/map.py, in the Map class
@ -352,24 +316,11 @@ class Room(DefaultRoom):
return string
```
Obviously this method of generating maps doesn't take into account of any doors or exits that are
hidden.. etc.. but hopefully it serves as a good base to start with. Like previously mentioned, it
is very important to have a solid foundation on rooms before implementing this. You can try this on
vanilla evennia by using @tunnel and essentially you can just create a long straight/edgy non-
looping rooms that will show on your in-game map.
Obviously this method of generating maps doesn't take into account of any doors or exits that are hidden.. etc.. but hopefully it serves as a good base to start with. Like previously mentioned, it is very important to have a solid foundation on rooms before implementing this. You can try this on vanilla evennia by using @tunnel and essentially you can just create a long straight/edgy non- looping rooms that will show on your in-game map.
The above example will display the map above the room description. You could also use an
[EvTable](github:evennia.utils.evtable) to place description and map next to each other. Some other
things you can do is to have a [Command](../Components/Commands.md) that displays with a larger radius, maybe with a
legend and other features.
The above example will display the map above the room description. You could also use an [EvTable](github:evennia.utils.evtable) to place description and map next to each other. Some other things you can do is to have a [Command](../Components/Commands.md) that displays with a larger radius, maybe with a legend and other features.
Below is the whole `map.py` for your reference. You need to update your `Room` typeclass (see above)
to actually call it. Remember that to see different symbols for a location you also need to set the
`sector_type` Attribute on the room to one of the keys in the `SYMBOLS` dictionary. So in this
example, to make a room be mapped as `[.]` you would set the room's `sector_type` to
`"SECT_INSIDE"`. Try it out with `@set here/sector_type = "SECT_INSIDE"`. If you wanted all new
rooms to have a given sector symbol, you could change the default in the `SYMBOLS` dictionary below,
or you could add the Attribute in the Room's `at_object_creation` method.
Below is the whole `map.py` for your reference. You need to update your `Room` typeclass (see above) to actually call it. Remember that to see different symbols for a location you also need to set the `sector_type` Attribute on the room to one of the keys in the `SYMBOLS` dictionary. So in this example, to make a room be mapped as `[.]` you would set the room's `sector_type` to `"SECT_INSIDE"`. Try it out with `@set here/sector_type = "SECT_INSIDE"`. If you wanted all new rooms to have a given sector symbol, you could change the default in the `SYMBOLS` dictionary below, or you could add the Attribute in the Room's `at_object_creation` method.
```python
# mygame/world/map.py

View file

@ -43,8 +43,7 @@ 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.
> 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

View file

@ -1,96 +0,0 @@
# Tutorial Tweeting Game Stats
This tutorial will create a simple script that will send a tweet to your already configured twitter
account. Please see: [How to connect Evennia to Twitter](../Setup/Channels-to-Twitter.md) if you
haven't already done so.
The script could be expanded to cover a variety of statistics you might wish to tweet about
regularly, from player deaths to how much currency is in the economy etc.
```python
# evennia/typeclasses/tweet_stats.py
import twitter
from random import randint
from django.conf import settings
from evennia import ObjectDB
from evennia import spawner
from evennia import logger
from evennia import DefaultScript
class TweetStats(DefaultScript):
"""
This implements the tweeting of stats to a registered twitter account
"""
# standard Script hooks
def at_script_creation(self):
"Called when script is first created"
self.key = "tweet_stats"
self.desc = "Tweets interesting stats about the game"
self.interval = 86400 # 1 day timeout
self.start_delay = False
def at_repeat(self):
"""
This is called every self.interval seconds to tweet interesting stats about the game.
"""
api = twitter.Api(consumer_key='consumer_key',
consumer_secret='consumer_secret',
access_token_key='access_token_key',
access_token_secret='access_token_secret')
number_tweet_outputs = 2
tweet_output = randint(1, number_tweet_outputs)
if tweet_output == 1:
##Game Chars, Rooms, Objects taken from @stats command
nobjs = ObjectDB.objects.count()
base_char_typeclass = settings.BASE_CHARACTER_TYPECLASS
nchars = ObjectDB.objects.filter(db_typeclass_path=base_char_typeclass).count()
nrooms =
ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=base_char_typeclass).count()
nexits = ObjectDB.objects.filter(db_location__isnull=False,
db_destination__isnull=False).count()
nother = nobjs - nchars - nrooms - nexits
tweet = "Chars: %s, Rooms: %s, Objects: %s" %(nchars, nrooms, nother)
else:
if tweet_output == 2: ##Number of prototypes and 3 random keys - taken from @spawn
command
prototypes = spawner.spawn(return_prototypes=True)
keys = prototypes.keys()
nprots = len(prototypes)
tweet = f"Prototype Count: {nprots} Random Keys: "
tweet += f" {keys[randint(0,len(keys)-1)]}"
for x in range(0,2): ##tweet 3
tweet += f", {keys[randint(0,len(keys)-1)]}"
# post the tweet
try:
response = api.PostUpdate(tweet)
except:
logger.log_trace(f"Tweet Error: When attempting to tweet {tweet}")
```
In the `at_script_creation` method, we configure the script to fire immediately (useful for testing)
and setup the delay (1 day) as well as script information seen when you use `@scripts`
In the `at_repeat` method (which is called immediately and then at interval seconds later) we setup
the Twitter API (just like in the initial configuration of twitter). numberTweetOutputs is used to
show how many different types of outputs we have (in this case 2). We then build the tweet based on
randomly choosing between these outputs.
1. Shows the number of Player Characters, Rooms and Other/Objects
2. Shows the number of prototypes currently in the game and then selects 3 random keys to show
[Scripts Information](../Components/Scripts.md) will show you how to add it as a Global script, however, for testing
it may be useful to start/stop it quickly from within the game. Assuming that you create the file
as `mygame/typeclasses/tweet_stats.py` it can be started by using the following command
@script Here = tweet_stats.TweetStats

View file

@ -1,4 +1,4 @@
# Arxcode installing help
# Using the Arxcode game dir
```{warning} Arxcode is separately maintained.

View file

@ -1,24 +1,19 @@
# Weather Tutorial
# Adding Weather messages to a Room
This tutorial will have us create a simple weather system for our MUD. The way we want to use this
is to have all outdoor rooms echo weather-related messages to the room at regular and semi-random
intervals. Things like "Clouds gather above", "It starts to rain" and so on.
This tutorial will have us create a simple weather system for our MUD. The way we want to use this is to have all outdoor rooms echo weather-related messages to the room at regular and semi-random intervals. Things like "Clouds gather above", "It starts to rain" and so on.
One could imagine every outdoor room in the game having a script running on themselves that fires
regularly. For this particular example it is however more efficient to do it another way, namely by
using a "ticker-subscription" model. The principle is simple: Instead of having each Object
individually track the time, they instead subscribe to be called by a global ticker who handles time
keeping. Not only does this centralize and organize much of the code in one place, it also has less
computing overhead.
One could imagine every outdoor room in the game having a script running on themselves that fires regularly. For this particular example it is however more efficient to do it another way, namely by using a "ticker-subscription" model.
Evennia offers the [TickerHandler](../Components/TickerHandler.md) specifically for using the subscription model. We
will use it for our weather system.
The principle is simple: Instead of having each Object individually track the time, they instead subscribe to be called by a global ticker who handles time keeping. Not only does this centralize and organize much of the code in one place, it also has less computing overhead.
Evennia's [TickerHandler](../Components/TickerHandler.md) specifically offers such a subscription model. We will use it for our weather system.
We will assume you know how to make your own Typeclasses. If not see one of the beginning tutorials.
We will create a new WeatherRoom typeclass that is aware of the day-night cycle.
```python
```{code-block} python
:linenos:
:emphasize-lines:
import random
from evennia import DefaultRoom, TICKER_HANDLER
@ -42,6 +37,8 @@ We will create a new WeatherRoom typeclass that is aware of the day-night cycle.
self.msg_contents(echo)
```
In the `at_object_creation` method, we simply added ourselves to the TickerHandler and tell it to
call `at_weather_update` every hour (`60*60` seconds). During testing you might want to play with a
shorter time duration.

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](Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Intro.md) tutorial
The tutorial starts from scratch. If you did the [First Steps Coding](Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Overview.md) tutorial
already you should have some ideas about how to do some of the steps already.
The following are the (very simplistic and cut-down) features we will implement (this was taken from

View file

@ -1,7 +1,7 @@
# Add a wiki on your website
**Before doing this tutorial you will probably want to read the intro in
[Basic Web tutorial](Beginner-Tutorial/Part5/Web-Tutorial.md).** Reading the three first parts of the
[Basic Web tutorial](./Web-Changing-Webpage.md).** Reading the three first parts of the
[Django tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial01/) might help as well.
This tutorial will provide a step-by-step process to installing a wiki on your website.

View file

@ -1,31 +1,17 @@
# Web Tutorial
# Changing the Game Website
Evennia uses the [Django](https://www.djangoproject.com/) web framework as the basis of both its
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.md) gives a more explicit example of making a
custom web page connected to your game, and you may want to read that after finishing this guide.
Evennia uses the [Django](https://www.djangoproject.com/) web framework as the basis of both its 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.md) gives a more explicit example of making a custom web page connected to your game, and you may want to read that after finishing this guide.
## A Basic Overview
Django is a web framework. It gives you a set of development tools for building a website quickly
and easily.
Django is a web framework. It gives you a set of development tools for building a website quickly and easily.
Django projects are split up into *apps* and these apps all contribute to one project. For instance,
you might have an app for conducting polls, or an app for showing news posts or, like us, one for
creating a web client.
Each of these applications has a `urls.py` file, which specifies what
[URL](https://en.wikipedia.org/wiki/Uniform_resource_locator)s are used by the app, a `views.py` file
for the code that the URLs activate, a `templates` directory for displaying the results of that code
in [HTML](https://en.wikipedia.org/wiki/Html) for the user, and a `static` folder that holds assets
like [CSS](https://en.wikipedia.org/wiki/CSS), [Javascript](https://en.wikipedia.org/wiki/Javascript),
and Image files (You may note your mygame/web folder does not have a `static` or `template` folder.
This is intended and explained further below). Django applications may also have a `models.py` file
for storing information in the database. We will not change any models here, take a look at the
[New Models](../../../Concepts/New-Models.md) page (as well as the [Django docs](https://docs.djangoproject.com/en/1.7/topics/db/models/) on models) if you are interested.
Each of these applications has a `urls.py` file, which specifies what [URL](https://en.wikipedia.org/wiki/Uniform_resource_locator)s are used by the app, a `views.py` file for the code that the URLs activate, a `templates` directory for displaying the results of that code in [HTML](https://en.wikipedia.org/wiki/Html) for the user, and a `static` folder that holds assets like [CSS](https://en.wikipedia.org/wiki/CSS), [Javascript](https://en.wikipedia.org/wiki/Javascript), and Image files (You may note your mygame/web folder does not have a `static` or `template` folder. This is intended and explained further below). Django applications may also have a `models.py` file for storing information in the database. We will not change any models here, take a look at the [New Models](../Concepts/New-Models.md) page (as well as the [Django docs](https://docs.djangoproject.com/en/1.7/topics/db/models/) on models) if you are interested.
There is also a root `urls.py` that determines the URL structure for the entire project. A starter
`urls.py` is included in the default game template, and automatically imports all of Evennia's
@ -80,32 +66,18 @@ evennia collectstatic
to only update the static files without any other changes.
> **Note**: Evennia will collect static files automatically during startup. So if `evennia
collectstatic` reports finding 0 files to collect, make sure you didn't start the engine at some
point - if so the collector has already done its work! To make sure, connect to the website and
check so the logo has actually changed to your own version.
> Evennia will collect static files automatically during startup. So if `evennia collectstatic` reports finding 0 files to collect, make sure you didn't start the engine at some point - if so the collector has already done its work! To make sure, connect to the website and check so the logo has actually changed to your own version.
> **Note**: Sometimes the static asset collector can get confused. If no matter what you do, your
overridden files aren't getting copied over the defaults, try removing the target file (or
everything) in the `web/static` directory, and re-running `collectstatic` to gather everything from
scratch.
> Sometimes the static asset collector can get confused. If no matter what you do, your overridden files aren't getting copied over the defaults, try removing the target file (or everything) in the `web/static` directory, and re-running `collectstatic` to gather everything from scratch.
## Changing the Front Page's Text
The default front page for Evennia contains information about the Evennia project. You'll probably
want to replace this information with information about your own project. Changing the page template
is done in a similar way to changing static resources.
The default front page for Evennia contains information about the Evennia project. You'll probably want to replace this information with information about your own project. Changing the page template is done in a similar way to changing static resources.
Like static files, Django looks through a series of template folders to find the file it wants. The
difference is that Django does not copy all of the template files into one place, it just searches
through the template folders until it finds a template that matches what it's looking for. This
means that when you edit a template, the changes are instant. You don't have to reload the server or
Like static files, Django looks through a series of template folders to find the file it wants. The difference is that Django does not copy all of the template files into one place, it just searches through the template folders until it finds a template that matches what it's looking for. This means that when you edit a template, the changes are instant. You don't have to reload the server or
run any extra commands to see these changes - reloading the web page in your browser is enough.
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.md). For now, you should know that the template we want to change
is stored in `evennia/web/website/templates/website/index.html`.
To replace 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.md). For now, you should know that the template we want to change is stored in `evennia/web/website/templates/website/index.html`.
To replace this template file, you will put your changed template inside the
`web/template_overrides/website` directory in your game folder. In the same way as with static
@ -119,8 +91,4 @@ 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.md) where you learn to make a web page that
displays in-game character stats. You can also look at [Django's own
tutorial](https://docs.djangoproject.com/en/1.7/intro/tutorial01/) to get more insight in how Django
works and what possibilities exist.
For further hints on working with the web presence, you could now continue to the [Web-based Character view Tutorial](./Web-Character-View-Tutorial.md) where you learn to make a web page that displays in-game character stats. You can also look at [Django's own tutorial](https://docs.djangoproject.com/en/1.7/intro/tutorial01/) to get more insight in how Django works and what possibilities exist.

View file

@ -0,0 +1,86 @@
# Automatically Tweet game stats
This tutorial will create a simple script that will send a tweet to your already configured twitter account. Please see: [How to connect Evennia to Twitter](../Setup/Channels-to-Twitter.md) if you haven't already done so.
The script could be expanded to cover a variety of statistics you might wish to tweet about
regularly, from player deaths to how much currency is in the economy etc.
```python
# evennia/typeclasses/tweet_stats.py
import twitter
from random import randint
from django.conf import settings
from evennia import ObjectDB
from evennia.prototypes import prototypes
from evennia import logger
from evennia import DefaultScript
class TweetStats(DefaultScript):
"""
This implements the tweeting of stats to a registered twitter account
"""
# standard Script hooks
def at_script_creation(self):
"Called when script is first created"
self.key = "tweet_stats"
self.desc = "Tweets interesting stats about the game"
self.interval = 86400 # 1 day timeout
self.start_delay = False
def at_repeat(self):
"""
This is called every self.interval seconds to
tweet interesting stats about the game.
"""
api = twitter.Api(consumer_key='consumer_key',
consumer_secret='consumer_secret',
access_token_key='access_token_key',
access_token_secret='access_token_secret')
# Game Chars, Rooms, Objects taken from `stats` command
nobjs = ObjectDB.objects.count()
base_char_typeclass = settings.BASE_CHARACTER_TYPECLASS
nchars = (
ObjectDB.objects
.filter(db_typeclass_path=base_char_typeclass)
.count()
)
nrooms =(
ObjectDB.objects
.filter(db_location__isnull=True)
.exclude(db_typeclass_path=base_char_typeclass)
.count()
)
nexits = (
ObjectDB.objects
.filter(db_location__isnull=False,
db_destination__isnull=False)
.count()
)
nother = nobjs - nchars - nrooms - nexits
tweet = f"Chars: {ncars}, Rooms: {nrooms}, Objects: {nother}"
# post the tweet
try:
response = api.PostUpdate(tweet)
except:
logger.log_trace(f"Tweet Error: When attempting to tweet {tweet}")
```
In the `at_script_creation` method, we configure the script to fire immediately (useful for testing)
and setup the delay (1 day) as well as script information seen when you use `@scripts`
In the `at_repeat` method (which is called immediately and then at interval seconds later) we setup
the Twitter API (just like in the initial configuration of twitter). We then show the number of Player Characters, Rooms and Other/Objects.
The [Scripts docs](../Components/Scripts.md) will show you how to add it as a Global script, however, for testing
it may be useful to start/stop it quickly from within the game. Assuming that you create the file
as `mygame/typeclasses/tweet_stats.py` it can be started by using the following command
script Here = tweet_stats.TweetStats