mirror of
https://github.com/evennia/evennia.git
synced 2026-04-03 22:47:16 +02:00
Updated HTML docs
This commit is contained in:
parent
66d0ad0bc9
commit
7900aad365
2073 changed files with 32986 additions and 41197 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|
||||
**Before doing this tutorial you will probably want to read the intro in
|
||||
[Basic Web tutorial](Starting/Part5/Web-Tutorial).** Reading the three first parts of the
|
||||
[Basic Web tutorial](Starting/Part5/Web-Tutorial.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.
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ Before we continue, let’s make a brief detour. Evennia is very flexible about
|
|||
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) represents the real person logging in and has no game-world existence.
|
||||
- Any [Object](../Components/Objects) can be puppeted by an Account (with proper permissions).
|
||||
- [Characters](../Components/Objects#characters), [Rooms](../Components/Objects#rooms), and [Exits](../Components/Objects#exits) are just
|
||||
- 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.
|
||||
- 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:
|
||||
|
|
@ -121,7 +121,7 @@ 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) (CmdSet) is a container
|
||||
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
|
||||
|
|
@ -174,7 +174,7 @@ This is great for testing. The way we added it, the MechCmdSet will even go away
|
|||
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) is a near-normal Python class that stores its existence to the database
|
||||
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:
|
||||
|
||||
```python
|
||||
|
|
|
|||
|
|
@ -7,28 +7,28 @@ exists before answering - maybe you can clarify that answer rather than to make
|
|||
|
||||
## Table of Contents
|
||||
|
||||
- [Removing default commands](./Coding-FAQ#removing-default-commands)
|
||||
- [Preventing character from moving based on a condition](./Coding-FAQ#preventing-character-from-
|
||||
- [Removing default commands](./Coding-FAQ.md#removing-default-commands)
|
||||
- [Preventing character from moving based on a condition](./Coding-FAQ.md#preventing-character-from-
|
||||
moving-based-on-a-condition)
|
||||
- [Reference initiating object in an EvMenu command](./Coding-FAQ#reference-initiating-object-in-an-
|
||||
- [Reference initiating object in an EvMenu command](./Coding-FAQ.md#reference-initiating-object-in-an-
|
||||
evmenu-command)
|
||||
- [Adding color to default Evennia Channels](./Coding-FAQ#adding-color-to-default-evennia-channels)
|
||||
- [Selectively turn off commands in a room](./Coding-FAQ#selectively-turn-off-commands-in-a-room)
|
||||
- [Select Command based on a condition](./Coding-FAQ#select-command-based-on-a-condition)
|
||||
- [Automatically updating code when reloading](./Coding-FAQ#automatically-updating-code-when-
|
||||
- [Adding color to default Evennia Channels](./Coding-FAQ.md#adding-color-to-default-evennia-channels)
|
||||
- [Selectively turn off commands in a room](./Coding-FAQ.md#selectively-turn-off-commands-in-a-room)
|
||||
- [Select Command based on a condition](./Coding-FAQ.md#select-command-based-on-a-condition)
|
||||
- [Automatically updating code when reloading](./Coding-FAQ.md#automatically-updating-code-when-
|
||||
reloading)
|
||||
- [Changing all exit messages](./Coding-FAQ#changing-all-exit-messages)
|
||||
- [Add parsing with the "to" delimiter](./Coding-FAQ#add-parsing-with-the-to-delimiter)
|
||||
- [Store last used session IP address](./Coding-FAQ#store-last-used-session-ip-address)
|
||||
- [Use wide characters with EvTable](./Coding-FAQ#non-latin-characters-in-evtable)
|
||||
- [Changing all exit messages](./Coding-FAQ.md#changing-all-exit-messages)
|
||||
- [Add parsing with the "to" delimiter](./Coding-FAQ.md#add-parsing-with-the-to-delimiter)
|
||||
- [Store last used session IP address](./Coding-FAQ.md#store-last-used-session-ip-address)
|
||||
- [Use wide characters with EvTable](./Coding-FAQ.md#non-latin-characters-in-evtable)
|
||||
|
||||
## Removing default commands
|
||||
**Q:** How does one *remove* (not replace) e.g. the default `get` [Command](../Components/Commands) from the
|
||||
Character [Command Set](../Components/Command-Sets)?
|
||||
**Q:** How does one *remove* (not replace) e.g. the default `get` [Command](../Components/Commands.md) from the
|
||||
Character [Command Set](../Components/Command-Sets.md)?
|
||||
|
||||
**A:** Go to `mygame/commands/default_cmdsets.py`. Find the `CharacterCmdSet` class. It has one
|
||||
method named `at_cmdset_creation`. At the end of that method, add the following line:
|
||||
`self.remove(default_cmds.CmdGet())`. See the [Adding Commands Tutorial](Starting/Part1/Adding-Commands)
|
||||
`self.remove(default_cmds.CmdGet())`. See the [Adding Commands Tutorial](Starting/Part1/Adding-Commands.md)
|
||||
for more info.
|
||||
|
||||
## Preventing character from moving based on a condition
|
||||
|
|
@ -36,7 +36,7 @@ for more info.
|
|||
combat, immobilized, etc.)
|
||||
|
||||
**A:** The `at_before_move` hook is called by Evennia just before performing any move. If it returns
|
||||
`False`, the move is aborted. Let's say we want to check for an [Attribute](../Components/Attributes) `cantmove`.
|
||||
`False`, the move is aborted. Let's say we want to check for an [Attribute](../Components/Attributes.md) `cantmove`.
|
||||
Add the following code to the `Character` class:
|
||||
|
||||
```python
|
||||
|
|
@ -52,7 +52,7 @@ def at_before_move(self, destination):
|
|||
**Q:** An object has a Command on it starts up an EvMenu instance. How do I capture a reference to
|
||||
that object for use in the menu?
|
||||
|
||||
**A:** When an [EvMenu](../Components/EvMenu) is started, the menu object is stored as `caller.ndb._menutree`.
|
||||
**A:** When an [EvMenu](../Components/EvMenu.md) is started, the menu object is stored as `caller.ndb._menutree`.
|
||||
This is a good place to store menu-specific things since it will clean itself up when the menu
|
||||
closes. When initiating the menu, any additional keywords you give will be available for you as
|
||||
properties on this menu object:
|
||||
|
|
@ -100,7 +100,7 @@ CHANNEL_COLORS`.
|
|||
**Q:** I want certain commands to turn off in a given room. They should still work normally for
|
||||
staff.
|
||||
|
||||
**A:** This is done using a custom cmdset on a room [locked with the 'call' lock type](../Components/Locks). Only
|
||||
**A:** This is done using a custom cmdset on a room [locked with the 'call' lock type](../Components/Locks.md). Only
|
||||
if this lock is passed will the commands on the room be made available to an object inside it. Here
|
||||
is an example of a room where certain commands are disabled for non-staff:
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ superusers).
|
|||
command to only be available on a full moon, from midnight to three in-game time.
|
||||
|
||||
**A:** This is easiest accomplished by putting the "werewolf" command on the Character as normal,
|
||||
but to [lock](../Components/Locks) it with the "cmd" type lock. Only if the "cmd" lock type is passed will the
|
||||
but to [lock](../Components/Locks.md) it with the "cmd" type lock. Only if the "cmd" lock type is passed will the
|
||||
command be available.
|
||||
|
||||
```python
|
||||
|
|
@ -156,8 +156,8 @@ class CmdWerewolf(Command):
|
|||
def func(self):
|
||||
# ...
|
||||
```
|
||||
Add this to the [default cmdset as usual](Starting/Part1/Adding-Commands). The `is_full_moon` [lock
|
||||
function](Locks#lock-functions) does not yet exist. We must create that:
|
||||
Add this to the [default cmdset as usual](Starting/Part1/Adding-Commands.md). The `is_full_moon` [lock
|
||||
function](../Components/Locks.md#lock-functions) does not yet exist. We must create that:
|
||||
|
||||
```python
|
||||
# in mygame/server/conf/lockfuncs.py
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ a while. Such effects are called *cooldowns*.
|
|||
|
||||
This page exemplifies a very resource-efficient way to do cooldowns. A more
|
||||
'active' way is to use asynchronous delays as in the [command duration
|
||||
tutorial](Command-Duration#Blocking-Commands), the two might be useful to
|
||||
tutorial](./Command-Duration.md#blocking-commands), the two might be useful to
|
||||
combine if you want to echo some message to the user after the cooldown ends.
|
||||
|
||||
## Non-persistent cooldown
|
||||
|
|
@ -88,7 +88,7 @@ database, you need to use the caster for the storage.
|
|||
self.caller.db.firestorm_lastcast = now
|
||||
```
|
||||
|
||||
Since we are storing as an [Attribute](../Components/Attributes), we need to identify the
|
||||
Since we are storing as an [Attribute](../Components/Attributes.md), we need to identify the
|
||||
variable as `firestorm_lastcast` so we are sure we get the right one (we'll
|
||||
likely have other skills with cooldowns after all). But this method of
|
||||
using cooldowns also has the advantage of working *between* commands - you can
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|
||||
Before reading this tutorial, if you haven't done so already, you might want to
|
||||
read [the documentation on commands](../Components/Commands) to get a basic understanding of
|
||||
read [the documentation on commands](../Components/Commands.md) to get a basic understanding of
|
||||
how commands work in Evennia.
|
||||
|
||||
In some types of games a command should not start and finish immediately.
|
||||
|
|
@ -40,7 +40,7 @@ class CmdTest(Command):
|
|||
> Important: The `yield` functionality will *only* work in the `func` method of
|
||||
> Commands. It only works because Evennia has especially
|
||||
> catered for it in Commands. If you want the same functionality elsewhere you
|
||||
> must use the [interactive decorator](../Concepts/Async-Process#The-@interactive-decorator).
|
||||
> must use the [interactive decorator](../Concepts/Async-Process.md#the-interactive-decorator).
|
||||
|
||||
The important line is the `yield 10`. It tells Evennia to "pause" the command
|
||||
and to wait for 10 seconds to execute the rest. If you add this command and
|
||||
|
|
@ -187,7 +187,7 @@ start crafting a shield at the same time, or if you just did a huge power-swing
|
|||
should not be able to do it again immediately.
|
||||
|
||||
The simplest way of implementing blocking is to use the technique covered in the [Command
|
||||
Cooldown](Command-Cooldown) tutorial. In that tutorial we implemented cooldowns by having the
|
||||
Cooldown](./Command-Cooldown.md) tutorial. In that tutorial we implemented cooldowns by having the
|
||||
Command store the current time. Next time the Command was called, we compared the current time to
|
||||
the stored time to determine if enough time had passed for a renewed use. This is a *very*
|
||||
efficient, reliable and passive solution. The drawback is that there is nothing to tell the Player
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ You can combine the sending of normal text with the sending (updating of the pro
|
|||
self.msg("This is a text", prompt="This is a prompt")
|
||||
```
|
||||
|
||||
You can update the prompt on demand, this is normally done using [OOB](../Concepts/OOB)-tracking of the relevant
|
||||
You can update the prompt on demand, this is normally done using [OOB](../Concepts/OOB.md)-tracking of the relevant
|
||||
Attributes (like the character's health). You could also make sure that attacking commands update
|
||||
the prompt when they cause a change in health, for example.
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ instance.
|
|||
## Coordinates as tags
|
||||
|
||||
The first concept might be the most surprising at first glance: we will create coordinates as
|
||||
[tags](../Components/Tags).
|
||||
[tags](../Components/Tags.md).
|
||||
|
||||
> Why not attributes, wouldn't that be easier?
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
|
||||
Evennia allows for exits to have any name. The command "kitchen" is a valid exit name as well as
|
||||
"jump out the window" or "north". An exit actually consists of two parts: an [Exit Object](../Components/Objects)
|
||||
and an [Exit Command](../Components/Commands) stored on said exit object. The command has the same key and aliases
|
||||
"jump out the window" or "north". An exit actually consists of two parts: an [Exit Object](../Components/Objects.md)
|
||||
and an [Exit Command](../Components/Commands.md) stored on said exit object. The command has the same key and aliases
|
||||
as the object, which is why you can see the exit in the room and just write its name to traverse it.
|
||||
|
||||
If you try to enter the name of a non-existing exit, it is thus the same as trying a non-exising
|
||||
|
|
@ -24,7 +24,7 @@ error message just told us that we couldn't go there.
|
|||
|
||||
## Adding default error commands
|
||||
|
||||
To solve this you need to be aware of how to [write and add new commands](Starting/Part1/Adding-Commands).
|
||||
To solve this you need to be aware of how to [write and add new commands](Starting/Part1/Adding-Commands.md).
|
||||
What you need to do is to create new commands for all directions you want to support in your game.
|
||||
In this example all we'll do is echo an error message, but you could certainly consider more
|
||||
advanced uses. You add these commands to the default command set. Here is an example of such a set
|
||||
|
|
@ -90,7 +90,7 @@ commands:
|
|||
You cannot move east.
|
||||
|
||||
Further expansions by the exit system (including manipulating the way the Exit command itself is
|
||||
created) can be done by modifying the [Exit typeclass](../Components/Typeclasses) directly.
|
||||
created) can be done by modifying the [Exit typeclass](../Components/Typeclasses.md) directly.
|
||||
|
||||
## Additional Comments
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ So why didn't we create a single error command above? Something like this:
|
|||
The anwer is that this would *not* work and understanding why is important in order to not be
|
||||
confused when working with commands and command sets.
|
||||
|
||||
The reason it doesn't work is because Evennia's [command system](../Components/Commands) compares commands *both*
|
||||
The reason it doesn't work is because Evennia's [command system](../Components/Commands.md) compares commands *both*
|
||||
by `key` and by `aliases`. If *either* of those match, the two commands are considered *identical*
|
||||
as far as cmdset merging system is concerned.
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ all that important for a text-based game. The main advantages of Python are an e
|
|||
development cycle and easy ways to create game systems. Doing the same with C can take many times
|
||||
more code and be harder to make stable and maintainable.
|
||||
|
||||
### Core Differences
|
||||
## Core Differences
|
||||
|
||||
- As mentioned, the main difference between Evennia and a Diku-derived codebase is that Evennia is
|
||||
written purely in Python. Since Python is an interpreted language there is no compile stage. It is
|
||||
|
|
@ -28,7 +28,7 @@ while writing to a flatfile it may become corrupt and the data lost. A proper da
|
|||
not susceptible to this - at no point is the data in a state where it cannot be recovered. Databases
|
||||
are also highly optimized for querying large data sets efficiently.
|
||||
|
||||
### Some Familiar Things
|
||||
## Some Familiar Things
|
||||
|
||||
Diku expresses the character object referenced normally by:
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ class CmdMyCmd(Command):
|
|||
"""
|
||||
This is a Command Evennia Object
|
||||
"""
|
||||
|
||||
|
||||
[...]
|
||||
|
||||
def func(self):
|
||||
|
|
@ -136,7 +136,7 @@ Puff~
|
|||
Puff the Fractal Dragon is here, contemplating a higher reality.
|
||||
~
|
||||
Is that some type of differential curve involving some strange, and unknown
|
||||
calculus that she seems to be made out of?
|
||||
calculus that she seems to be made out of?
|
||||
~
|
||||
516106 0 0 0 2128 0 0 0 1000 E
|
||||
34 9 -10 6d6+340 5d5+5
|
||||
|
|
@ -145,7 +145,7 @@ calculus that she seems to be made out of?
|
|||
BareHandAttack: 12
|
||||
E
|
||||
T 95
|
||||
```
|
||||
```
|
||||
Each line represents something that the MUD reads in and does something with it. This isn't easy to
|
||||
read, but let's see if we can emulate this as a dictionary to be stored on a database script created
|
||||
in Evennia.
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ to use. So the *Player* usually operates by making use of the tools prepared for
|
|||
|
||||
For a *Player*, collaborating on a game need not be too different between MUSH and Evennia. The
|
||||
building and description of the game world can still happen mostly in-game using build commands,
|
||||
using text tags and [inline functions](../Concepts/TextTags#inline-functions) to prettify and customize the
|
||||
using text tags and [inline functions](../Components/FuncParser.md) to prettify and customize the
|
||||
experience. Evennia offers external ways to build a world but those are optional. There is also
|
||||
nothing *in principle* stopping a Developer from offering a softcode-like language to Players if
|
||||
that is deemed necessary.
|
||||
|
|
@ -88,7 +88,7 @@ based inheritance of MUSH.
|
|||
|
||||
There are other differences for sure, but that should give some feel for things. Enough with the
|
||||
theory. Let's get down to more practical matters next. To install, see the
|
||||
[Getting Started instructions](../Setup/Setup-Quickstart).
|
||||
[Getting Started instructions](../Setup/Setup-Quickstart.md).
|
||||
|
||||
## A first step making things more familiar
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ developer changing the underlying Python code.
|
|||
## Next steps
|
||||
|
||||
If you are a *Developer* and are interested in making a more MUSH-like Evennia game, a good start is
|
||||
to look into the Evennia [Tutorial for a first MUSH-like game](Starting/Part3/Tutorial-for-basic-MUSH-like-game).
|
||||
to look into the Evennia [Tutorial for a first MUSH-like game](Starting/Part3/Tutorial-for-basic-MUSH-like-game.md).
|
||||
That steps through building a simple little game from scratch and helps to acquaint you with the
|
||||
various corners of Evennia. There is also the [Tutorial for running roleplaying sessions](Evennia-
|
||||
for-roleplaying-sessions) that can be of interest.
|
||||
|
|
@ -211,8 +211,8 @@ for-roleplaying-sessions) that can be of interest.
|
|||
An important aspect of making things more familiar for *Players* is adding new and tweaking existing
|
||||
commands. How this is done is covered by the [Tutorial on adding new commands](Adding-Command-
|
||||
Tutorial). You may also find it useful to shop through the `evennia/contrib/` folder. The
|
||||
[Tutorial world](Starting/Part1/Tutorial-World-Introduction) is a small single-player quest you can try (it’s not very MUSH-
|
||||
like but it does show many Evennia concepts in action). Beyond that there are [many more tutorials](./Howto-Overview)
|
||||
[Tutorial world](Starting/Part1/Tutorial-World-Introduction.md) is a small single-player quest you can try (it’s not very MUSH-
|
||||
like but it does show many Evennia concepts in action). Beyond that there are [many more tutorials](./Howto-Overview.md)
|
||||
to try out. If you feel you want a more visual overview you can also look at
|
||||
[Evennia in pictures](https://evennia.blogspot.se/2016/05/evennia-in-pictures.html).
|
||||
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ defaults for our particular use-case. Below we will flesh out these components f
|
|||
|
||||
## Starting out
|
||||
|
||||
We will assume you start from scratch. You need Evennia installed, as per the [Setup Quickstart](../Setup/Setup-Quickstart)
|
||||
We will assume you start from scratch. You need Evennia installed, as per the [Setup Quickstart](../Setup/Setup-Quickstart.md)
|
||||
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](Starting/Part1/Starting-Part1) tutorial, just to see roughly where things are modified.
|
||||
[First Steps Coding](Starting/Part1/Starting-Part1.md) tutorial, just to see roughly where things are modified.
|
||||
|
||||
## The Game Master role
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ to show your renewed GM status to the other accounts.
|
|||
|
||||
### The permission hierarchy
|
||||
|
||||
Evennia has the following [permission hierarchy](../Concepts/Building-Permissions#assigning-permissions) out of
|
||||
Evennia has the following [permission hierarchy](../Concepts/Building-Permissions.md#assigning-permissions) out of
|
||||
the box: *Players, Helpers, Builders, Admins* and finally *Developers*. We could change these but
|
||||
then we'd need to update our Default commands to use the changes. We want to keep this simple, so
|
||||
instead we map our different roles on top of this permission ladder.
|
||||
|
|
@ -60,7 +60,7 @@ everyone.
|
|||
5. `Developers`-level permission are the server administrators, the ones with the ability to
|
||||
restart/shutdown the server as well as changing the permission levels.
|
||||
|
||||
> The [superuser](../Concepts/Building-Permissions#the-super-user) is not part of the hierarchy and actually
|
||||
> The [superuser](../Concepts/Building-Permissions.md#the-super-user) is not part of the hierarchy and actually
|
||||
completely bypasses it. We'll assume server admin(s) will "just" be Developers.
|
||||
|
||||
### How to grant permissions
|
||||
|
|
@ -102,7 +102,7 @@ its name will have the string`(GM)` added to the end.
|
|||
#### Character modification
|
||||
|
||||
Let's first start by customizing the Character. We recommend you browse the beginning of the
|
||||
[Account](../Components/Accounts) page to make sure you know how Evennia differentiates between the OOC "Account
|
||||
[Account](../Components/Accounts.md) page to make sure you know how Evennia differentiates between the OOC "Account
|
||||
objects" (not to be confused with the `Accounts` permission, which is just a string specifying your
|
||||
access) and the IC "Character objects".
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ Above, we change how the Character's name is displayed: If the account controlli
|
|||
a GM, we attach the string `(GM)` to the Character's name so everyone can tell who's the boss. If we
|
||||
ourselves are Developers or GM's we will see database ids attached to Characters names, which can
|
||||
help if doing database searches against Characters of exactly the same name. We base the "gm-
|
||||
ingness" on having an flag (an [Attribute](../Components/Attributes)) named `is_gm`. We'll make sure new GM's
|
||||
ingness" on having an flag (an [Attribute](../Components/Attributes.md)) named `is_gm`. We'll make sure new GM's
|
||||
actually get this flag below.
|
||||
|
||||
> **Extra exercise:** This will only show the `(GM)` text on *Characters* puppeted by a GM account,
|
||||
|
|
@ -151,7 +151,7 @@ that is, it will show only to those in the same location. If we wanted it to als
|
|||
|
||||
#### New @gm/@ungm command
|
||||
|
||||
We will describe in some detail how to create and add an Evennia [command](../Components/Commands) here with the
|
||||
We will describe in some detail how to create and add an Evennia [command](../Components/Commands.md) here with the
|
||||
hope that we don't need to be as detailed when adding commands in the future. We will build on
|
||||
Evennia's default "mux-like" commands here.
|
||||
|
||||
|
|
@ -266,7 +266,7 @@ We will here show two examples using the *EvTable* and *EvForm* utilities.Later
|
|||
Commands to edit and display the output from those utilities.
|
||||
|
||||
> Note that due to the limitations of the wiki, no color is used in any of the examples. See
|
||||
> [the text tag documentation](../Concepts/TextTags) for how to add color to the tables and forms.
|
||||
> [the text tag documentation](../Concepts/TextTags.md) for how to add color to the tables and forms.
|
||||
|
||||
#### Making a sheet with EvTable
|
||||
|
||||
|
|
@ -686,7 +686,7 @@ implemented.
|
|||
## Rooms
|
||||
|
||||
Evennia comes with rooms out of the box, so no extra work needed. A GM will automatically have all
|
||||
needed building commands available. A fuller go-through is found in the [Building tutorial](Starting/Part1/Building-Quickstart).
|
||||
needed building commands available. A fuller go-through is found in the [Building tutorial](Starting/Part1/Building-Quickstart.md).
|
||||
Here are some useful highlights:
|
||||
|
||||
* `@dig roomname;alias = exit_there;alias, exit_back;alias` - this is the basic command for digging
|
||||
|
|
@ -704,7 +704,7 @@ access after the fact.
|
|||
|
||||
## Channels
|
||||
|
||||
Evennia comes with [Channels](../Components/Communications#Channels) in-built and they are described fully in the
|
||||
Evennia comes with [Channels](../Components/Channels.md) in-built and they are described fully in the
|
||||
documentation. For brevity, here are the relevant commands for normal use:
|
||||
|
||||
* `@ccreate new_channel;alias;alias = short description` - Creates a new channel.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ names for its time units or might even use a completely custom calendar. You don
|
|||
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
|
||||
## A game time with a standard calendar
|
||||
|
||||
Many games let their in-game time run faster or slower than real time, but still use our normal
|
||||
real-world calendar. This is common both for games set in present day as well as for games in
|
||||
|
|
@ -21,7 +21,7 @@ automatically handled by the system.
|
|||
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
|
||||
### Setting up game time for a standard calendar
|
||||
|
||||
All is done through the settings. Here are the settings you should use if you want a game time with
|
||||
a standard calendar:
|
||||
|
|
@ -91,14 +91,14 @@ The line that is most relevant here is the game time epoch. You see it shown at
|
|||
this point forward, the game time keeps increasing. If you keep typing `@time`, you'll see the game
|
||||
time updated correctly... and going (by default) twice as fast as the real time.
|
||||
|
||||
#### Time-related events
|
||||
### 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 function `schedule()` should be used here. It will create a [script](../Components/Scripts) with some
|
||||
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.
|
||||
|
||||
|
|
@ -110,12 +110,12 @@ 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:
|
||||
Here is a short example for making the sun rise every day:
|
||||
|
||||
```python
|
||||
# in a file ingame_time.py in mygame/world/
|
||||
|
||||
from evennia.utils import gametime
|
||||
from evennia.utils import gametime
|
||||
from typeclasses.rooms import Room
|
||||
|
||||
def at_sunrise():
|
||||
|
|
@ -166,7 +166,7 @@ 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
|
||||
### 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.
|
||||
|
|
@ -194,7 +194,7 @@ 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.
|
||||
|
||||
So for our example, our settings may look like this:
|
||||
So for our example, our settings may look like this:
|
||||
|
||||
```python
|
||||
# in a file settings.py in mygame/server/conf
|
||||
|
|
@ -263,7 +263,7 @@ Don't forget to add it in your CharacterCmdSet to see this command:
|
|||
```python
|
||||
# in mygame/commands/default_cmdset.py
|
||||
|
||||
from commands.gametime import CmdTime # <-- Add
|
||||
from commands.gametime import CmdTime # <-- Add
|
||||
|
||||
# ...
|
||||
|
||||
|
|
@ -280,7 +280,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|||
Populates the cmdset
|
||||
"""
|
||||
super().at_cmdset_creation()
|
||||
# ...
|
||||
# ...
|
||||
self.add(CmdTime()) # <- Add
|
||||
```
|
||||
|
||||
|
|
@ -293,9 +293,9 @@ it, you might see something like:
|
|||
You could display it a bit more prettily with names for months and perhaps even days, if you want.
|
||||
And if "months" are called "moons" in your game, this is where you'd add that.
|
||||
|
||||
#### Time-related events in custom gametime
|
||||
## 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.
|
||||
same way as described for the default one above.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
The documents in this section aims to teach how to use Evennia in a tutorial or
|
||||
a step-by-step way. They often give hints on about solving a problem or implementing
|
||||
a particular feature or concept. They will often refer to the
|
||||
[components](../Components/Components-Overview) or [concepts](../Concepts/Concepts-Overview)
|
||||
[components](../Components/Components-Overview.md) or [concepts](../Concepts/Concepts-Overview.md)
|
||||
docs for those that want to dive deeper.
|
||||
|
||||
## The Starting Tutorial
|
||||
|
|
@ -14,92 +14,92 @@ in mind for your own game, this will give you a good start.
|
|||
|
||||
### Part 1: What we have
|
||||
|
||||
1. [Introduction & Overview](Starting/Part1/Starting-Part1)
|
||||
1. [Building stuff](Starting/Part1/Building-Quickstart)
|
||||
1. [The Tutorial World](Starting/Part1/Tutorial-World-Introduction)
|
||||
1. [Python basics](Starting/Part1/Python-basic-introduction)
|
||||
1. [Game dir overview](Starting/Part1/Gamedir-Overview)
|
||||
1. [Python classes and objects](Starting/Part1/Python-classes-and-objects)
|
||||
1. [Accessing the Evennia library](Starting/Part1/Evennia-Library-Overview)
|
||||
1. [Typeclasses - Persistent objects](Starting/Part1/Learning-Typeclasses)
|
||||
1. [Making our first own commands](Starting/Part1/Adding-Commands)
|
||||
1. [Parsing and replacing default Commands](Starting/Part1/More-on-Commands)
|
||||
1. [Creating things](Starting/Part1/Creating-Things)
|
||||
1. [Searching for things](Starting/Part1/Searching-Things)
|
||||
1. [Advanced searching with Django queries](Starting/Part1/Django-queries)
|
||||
1. [Introduction & Overview](Starting/Part1/Starting-Part1.md)
|
||||
1. [Building stuff](Starting/Part1/Building-Quickstart.md)
|
||||
1. [The Tutorial World](Starting/Part1/Tutorial-World-Introduction.md)
|
||||
1. [Python basics](Starting/Part1/Python-basic-introduction.md)
|
||||
1. [Game dir overview](Starting/Part1/Gamedir-Overview.md)
|
||||
1. [Python classes and objects](Starting/Part1/Python-classes-and-objects.md)
|
||||
1. [Accessing the Evennia library](Starting/Part1/Evennia-Library-Overview.md)
|
||||
1. [Typeclasses - Persistent objects](Starting/Part1/Learning-Typeclasses.md)
|
||||
1. [Making our first own commands](Starting/Part1/Adding-Commands.md)
|
||||
1. [Parsing and replacing default Commands](Starting/Part1/More-on-Commands.md)
|
||||
1. [Creating things](Starting/Part1/Creating-Things.md)
|
||||
1. [Searching for things](Starting/Part1/Searching-Things.md)
|
||||
1. [Advanced searching with Django queries](Starting/Part1/Django-queries.md)
|
||||
|
||||
### Part 2: What we want
|
||||
|
||||
1. [Introduction & Overview](Starting/Part2/Starting-Part2)
|
||||
1. [On planning a game](Starting/Part2/Game-Planning)
|
||||
1. [Planning to use some useful Contribs](Starting/Part2/Planning-Some-Useful-Contribs)
|
||||
1. [Introduction & Overview](Starting/Part2/Starting-Part2.md)
|
||||
1. [On planning a game](Starting/Part2/Game-Planning.md)
|
||||
1. [Planning to use some useful Contribs](Starting/Part2/Planning-Some-Useful-Contribs.md)
|
||||
|
||||
### Part3: How we get there
|
||||
|
||||
1. [Introduction & Overview](Starting/Part3/Starting-Part3)
|
||||
1. [Making a custom Character](Starting/Part3/Implementing-a-game-rule-system)
|
||||
1. [Character generation](../Unimplemented)
|
||||
1. [Resolving skills and challenges](../Unimplemented)
|
||||
1. [NPCs and mobiles](./Coordinates)
|
||||
1. [Quests and Zones](../Unimplemented)
|
||||
1. [A Combat system](../Unimplemented)
|
||||
1. [Introduction & Overview](Starting/Part3/Starting-Part3.md)
|
||||
1. [Making a custom Character](Starting/Part3/Implementing-a-game-rule-system.md)
|
||||
1. [Character generation](../Unimplemented.md)
|
||||
1. [Resolving skills and challenges](../Unimplemented.md)
|
||||
1. [NPCs and mobiles](./Coordinates.md)
|
||||
1. [Quests and Zones](../Unimplemented.md)
|
||||
1. [A Combat system](../Unimplemented.md)
|
||||
|
||||
### Part 4: Using what we created
|
||||
|
||||
1. [Introduction & Overview](Starting/Part4/Starting-Part4)
|
||||
1. [Building the tech demo](../Unimplemented)
|
||||
1. [Creating a game world](../Unimplemented)
|
||||
1. [Introduction & Overview](Starting/Part4/Starting-Part4.md)
|
||||
1. [Building the tech demo](../Unimplemented.md)
|
||||
1. [Creating a game world](../Unimplemented.md)
|
||||
|
||||
### Part 5: Showing the world
|
||||
|
||||
1. [Introduction & Overview](Starting/Part5/Starting-Part5)
|
||||
1. [Add a web page](Starting/Part5/Add-a-simple-new-web-page)
|
||||
1. [More on adding web features](Starting/Part5/Web-Tutorial)
|
||||
1. [Taking your game online](../Unimplemented)
|
||||
1. [Next steps](../Unimplemented)
|
||||
1. [Introduction & Overview](Starting/Part5/Starting-Part5.md)
|
||||
1. [Add a web page](Starting/Part5/Add-a-simple-new-web-page.md)
|
||||
1. [More on adding web features](Starting/Part5/Web-Tutorial.md)
|
||||
1. [Taking your game online](../Unimplemented.md)
|
||||
1. [Next steps](../Unimplemented.md)
|
||||
|
||||
|
||||
## FAQs
|
||||
|
||||
- [Coding FAQ](./Coding-FAQ)
|
||||
- [Coding FAQ](./Coding-FAQ.md)
|
||||
|
||||
## Howto's
|
||||
|
||||
- [Giving Exits a default error](./Default-Exit-Errors)
|
||||
- [Add a command prompt](./Command-Prompt)
|
||||
- [Don't allow spamming commands](./Command-Cooldown)
|
||||
- [Commands that take time](./Command-Duration)
|
||||
- [Configuring color](./Manually-Configuring-Color)
|
||||
- [Tweet game stats](./Tutorial-Tweeting-Game-Stats)
|
||||
- [Giving Exits a default error](./Default-Exit-Errors.md)
|
||||
- [Add a command prompt](./Command-Prompt.md)
|
||||
- [Don't allow spamming commands](./Command-Cooldown.md)
|
||||
- [Commands that take time](./Command-Duration.md)
|
||||
- [Configuring color](./Manually-Configuring-Color.md)
|
||||
- [Tweet game stats](./Tutorial-Tweeting-Game-Stats.md)
|
||||
|
||||
## Mobs and NPCs
|
||||
|
||||
- [NPCs that listen to you](./Tutorial-NPCs-listening)
|
||||
- [Mobs that attack you](./Tutorial-Aggressive-NPCs)
|
||||
- [Shopkeepers](./NPC-shop-Tutorial)
|
||||
- [NPCs that listen to you](./Tutorial-NPCs-listening.md)
|
||||
- [Mobs that attack you](./Tutorial-Aggressive-NPCs.md)
|
||||
- [Shopkeepers](./NPC-shop-Tutorial.md)
|
||||
|
||||
## Vehicles
|
||||
|
||||
- [Building a mech](./Building-a-mech-tutorial)
|
||||
- [Building a train](./Tutorial-Vehicles)
|
||||
- [Building a mech](./Building-a-mech-tutorial.md)
|
||||
- [Building a train](./Tutorial-Vehicles.md)
|
||||
|
||||
## Systems
|
||||
|
||||
- [Understanding In-game time](./Gametime-Tutorial)
|
||||
- [Understanding the Help system](./Help-System-Tutorial)
|
||||
- [Adding mass to objects](./Mass-and-weight-for-objects)
|
||||
- [Add weather](./Weather-Tutorial)
|
||||
- [Understanding In-game time](./Gametime-Tutorial.md)
|
||||
- [Understanding the Help system](./Help-System-Tutorial.md)
|
||||
- [Adding mass to objects](./Mass-and-weight-for-objects.md)
|
||||
- [Add weather](./Weather-Tutorial.md)
|
||||
|
||||
## Web-related tutorials
|
||||
|
||||
- [Add a wiki](./Add-a-wiki-on-your-website)
|
||||
- [A web-based character generation](./Web-Character-Generation)
|
||||
- [View Character on website](./Web-Character-View-Tutorial)
|
||||
- [Add a wiki](./Add-a-wiki-on-your-website.md)
|
||||
- [A web-based character generation](./Web-Character-Generation.md)
|
||||
- [View Character on website](./Web-Character-View-Tutorial.md)
|
||||
|
||||
## Deep-dives
|
||||
|
||||
- [Parsing command inputs](./Parsing-commands-tutorial)
|
||||
- [Understanding color-tags](./Understanding-Color-Tags)
|
||||
- [Play paper&pen RPGs online with Evennia](./Evennia-for-roleplaying-sessions)
|
||||
- [Evennia for Diku Users](./Evennia-for-Diku-Users)
|
||||
- [Evennia for MUSH-Users](./Evennia-for-MUSH-Users)
|
||||
- [Parsing command inputs](./Parsing-commands-tutorial.md)
|
||||
- [Understanding color-tags](./Understanding-Color-Tags.md)
|
||||
- [Play paper&pen RPGs online with Evennia](./Evennia-for-roleplaying-sessions.md)
|
||||
- [Evennia for Diku Users](./Evennia-for-Diku-Users.md)
|
||||
- [Evennia for MUSH-Users](./Evennia-for-MUSH-Users.md)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ This is a small tutorial for customizing your character objects, using the examp
|
|||
turn on and off ANSI color parsing as an example. `@options NOCOLOR=True` will now do what this
|
||||
tutorial shows, but the tutorial subject can be applied to other toggles you may want, as well.
|
||||
|
||||
In the Building guide's [Colors](../Concepts/TextTags#coloured-text) page you can learn how to add color to your
|
||||
In the Building guide's [Colors](../Concepts/Colors.md) page you can learn how to add color to your
|
||||
game by using special markup. Colors enhance the gaming experience, but not all users want color.
|
||||
Examples would be users working from clients that don't support color, or people with various seeing
|
||||
disabilities that rely on screen readers to play your game. Also, whereas Evennia normally
|
||||
|
|
@ -26,7 +26,7 @@ configuration system for your characters. This is the basic sequence:
|
|||
Create a new module in `mygame/typeclasses` named, for example, `mycharacter.py`. Alternatively you
|
||||
can simply add a new class to 'mygamegame/typeclasses/characters.py'.
|
||||
|
||||
In your new module(or characters.py), create a new [Typeclass](../Components/Typeclasses) inheriting from
|
||||
In your new module(or characters.py), create a new [Typeclass](../Components/Typeclasses.md) inheriting from
|
||||
`evennia.DefaultCharacter`. We will also import `evennia.utils.ansi`, which we will use later.
|
||||
|
||||
```python
|
||||
|
|
@ -39,7 +39,7 @@ In your new module(or characters.py), create a new [Typeclass](../Components/Typ
|
|||
self.db.config_color = True
|
||||
```
|
||||
|
||||
Above we set a simple config value as an [Attribute](../Components/Attributes).
|
||||
Above we set a simple config value as an [Attribute](../Components/Attributes.md).
|
||||
|
||||
Let's make sure that new characters are created of this type. Edit your
|
||||
`mygame/server/conf/settings.py` file and add/change `BASE_CHARACTER_TYPECLASS` to point to your new
|
||||
|
|
@ -158,7 +158,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|||
|
||||
## More colors
|
||||
|
||||
Apart from ANSI colors, Evennia also supports **Xterm256** colors (See [Colors](../Concepts/TextTags#colored-
|
||||
Apart from ANSI colors, Evennia also supports **Xterm256** colors (See [Colors](../Concepts/TextTags.md#colored-
|
||||
text)). The `msg()` method supports the `xterm256` keyword for manually activating/deactiving
|
||||
xterm256. It should be easy to expand the above example to allow players to customize xterm256
|
||||
regardless of if Evennia thinks their client supports it or not.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ determine a character's burden weight for travel speed... The total mass of an
|
|||
contribute to the force of a weapon swing, or a speeding meteor to give it a potential striking
|
||||
force.
|
||||
|
||||
#### Objects
|
||||
## Objects
|
||||
|
||||
Now that we have reasons for keeping track of object mass, let's look at the default object class
|
||||
inside your mygame/typeclasses/objects.py and see how easy it is to total up mass from an object and
|
||||
|
|
@ -36,7 +36,7 @@ default for Heavy types to something much larger than 1 gram or whatever unit yo
|
|||
non-default mass would be stored on the `mass` [[Attributes]] of the objects.
|
||||
|
||||
|
||||
#### Characters and rooms
|
||||
## Characters and rooms
|
||||
|
||||
You can add a `get_mass` definition to characters and rooms, also.
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ else:
|
|||
pass # Danger! Alarm sounds, cable snaps, elevator stops...
|
||||
```
|
||||
|
||||
#### Inventory
|
||||
## Inventory
|
||||
Example of listing mass of items in your inventory:
|
||||
|
||||
```python
|
||||
|
|
@ -95,4 +95,4 @@ class CmdInventory(MuxCommand):
|
|||
string = f"|wYou are carrying:\n{table}"
|
||||
self.caller.msg(string)
|
||||
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# NPC shop Tutorial
|
||||
|
||||
This tutorial will describe how to make an NPC-run shop. We will make use of the [EvMenu](../Components/EvMenu)
|
||||
This tutorial will describe how to make an NPC-run shop. We will make use of the [EvMenu](../Components/EvMenu.md)
|
||||
system to present shoppers with a menu where they can buy things from the store's stock.
|
||||
|
||||
Our shop extends over two rooms - a "front" room open to the shop's customers and a locked "store
|
||||
|
|
@ -19,13 +19,13 @@ compare it to the `gold` Attribute of the customer. If enough gold is available,
|
|||
deducted and the goods transferred from the store room to the inventory of the customer.
|
||||
- We will lock the store room so that only people with the right key can get in there.
|
||||
|
||||
### The shop menu
|
||||
## The shop menu
|
||||
|
||||
We want to show a menu to the customer where they can list, examine and buy items in the store. This
|
||||
menu should change depending on what is currently for sale. Evennia's *EvMenu* utility will manage
|
||||
the menu for us. It's a good idea to [read up on EvMenu](../Components/EvMenu) if you are not familiar with it.
|
||||
the menu for us. It's a good idea to [read up on EvMenu](../Components/EvMenu.md) if you are not familiar with it.
|
||||
|
||||
#### Designing the menu
|
||||
### Designing the menu
|
||||
|
||||
The shopping menu's design is straightforward. First we want the main screen. You get this when you
|
||||
enter a shop and use the `browse` or `buy` command:
|
||||
|
|
@ -64,7 +64,7 @@ You cannot afford 5 gold for A rusty sword!
|
|||
```
|
||||
After this you should be back to the top level of the shopping menu again and can continue browsing.
|
||||
|
||||
#### Coding the menu
|
||||
### Coding the menu
|
||||
|
||||
EvMenu defines the *nodes* (each menu screen with options) as normal Python functions. Each node
|
||||
must be able to change on the fly depending on what items are currently for sale. EvMenu will
|
||||
|
|
@ -164,10 +164,10 @@ need to return anything. In `buy_ware_result` we determine if the customer can a
|
|||
give proper return messages. This is also where we actually move the bought item into the inventory
|
||||
of the customer.
|
||||
|
||||
#### The command to start the menu
|
||||
### The command to start the menu
|
||||
|
||||
We could *in principle* launch the shopping menu the moment a customer steps into our shop room, but
|
||||
this would probably be considered pretty annoying. It's better to create a [Command](../Components/Commands) for
|
||||
this would probably be considered pretty annoying. It's better to create a [Command](../Components/Commands.md) for
|
||||
customers to explicitly wanting to shop around.
|
||||
|
||||
```python
|
||||
|
|
@ -200,7 +200,7 @@ class CmdBuy(Command):
|
|||
This will launch the menu. The `EvMenu` instance is initialized with the path to this very module -
|
||||
since the only global functions available in this module are our menu nodes, this will work fine
|
||||
(you could also have put those in a separate module). We now just need to put this command in a
|
||||
[CmdSet](../Components/Command-Sets) so we can add it correctly to the game:
|
||||
[CmdSet](../Components/Command-Sets.md) so we can add it correctly to the game:
|
||||
|
||||
```python
|
||||
from evennia import CmdSet
|
||||
|
|
@ -210,7 +210,7 @@ class ShopCmdSet(CmdSet):
|
|||
self.add(CmdBuy())
|
||||
```
|
||||
|
||||
### Building the shop
|
||||
## Building the shop
|
||||
|
||||
There are really only two things that separate our shop from any other Room:
|
||||
|
||||
|
|
@ -219,7 +219,7 @@ There are really only two things that separate our shop from any other Room:
|
|||
the shop.
|
||||
|
||||
For testing we could easily add these features manually to a room using `@py` or other admin
|
||||
commands. Just to show how it can be done we'll instead make a custom [Typeclass](../Components/Typeclasses) for
|
||||
commands. Just to show how it can be done we'll instead make a custom [Typeclass](../Components/Typeclasses.md) for
|
||||
the shop room and make a small command that builders can use to build both the shop and the
|
||||
storeroom at once.
|
||||
|
||||
|
|
@ -300,7 +300,7 @@ default-cmdset) before you can use it. Once having created the shop you can now
|
|||
`@open` a new exit to it. You could also easily expand the above command to automatically create
|
||||
exits to and from the new shop from your current location.
|
||||
|
||||
To avoid customers walking in and stealing everything, we create a [Lock](../Components/Locks) on the storage
|
||||
To avoid customers walking in and stealing everything, we create a [Lock](../Components/Locks.md) on the storage
|
||||
door. It's a simple lock that requires the one entering to carry an object named
|
||||
`<shopname>-storekey`. We even create such a key object and drop it in the shop for the new shop
|
||||
keeper to pick up.
|
||||
|
|
@ -312,7 +312,7 @@ you need to come up with a more robust lock-key solution.
|
|||
You could add better default descriptions as part of the `@buildshop` command or leave descriptions
|
||||
this up to the Builder.
|
||||
|
||||
### The shop is open for business!
|
||||
## The shop is open for business!
|
||||
|
||||
We now have a functioning shop and an easy way for Builders to create it. All you need now is to
|
||||
`@open` a new exit from the rest of the game into the shop and put some sell-able items in the store
|
||||
|
|
@ -328,7 +328,7 @@ would then be gone and the counter be wrong - the shop would pass us the next it
|
|||
|
||||
Fixing these issues are left as an exercise.
|
||||
|
||||
If you want to keep the shop fully NPC-run you could add a [Script](../Components/Scripts) to restock the shop's
|
||||
If you want to keep the shop fully NPC-run you could add a [Script](../Components/Scripts.md) to restock the shop's
|
||||
store room regularly. This shop example could also easily be owned by a human Player (run for them
|
||||
by a hired NPC) - the shop owner would get the key to the store room and be responsible for keeping
|
||||
it well stocked.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|
||||
This tutorial will elaborate on the many ways one can parse command arguments. The first step after
|
||||
[adding a command](Starting/Part1/Adding-Commands) usually is to parse its arguments. There are lots of
|
||||
[adding a command](Starting/Part1/Adding-Commands.md) usually is to parse its arguments. There are lots of
|
||||
ways to do it, but some are indeed better than others and this tutorial will try to present them.
|
||||
|
||||
If you're a Python beginner, this tutorial might help you a lot. If you're already familiar with
|
||||
|
|
@ -652,7 +652,7 @@ about... what is this `"book"`?
|
|||
|
||||
To get an object from a string, we perform an Evennia search. Evennia provides a `search` method on
|
||||
all typeclassed objects (you will most likely use the one on characters or accounts). This method
|
||||
supports a very wide array of arguments and has [its own tutorial](Starting/Part1/Searching-Things).
|
||||
supports a very wide array of arguments and has [its own tutorial](Starting/Part1/Searching-Things.md).
|
||||
Some examples of useful cases follow:
|
||||
|
||||
### Local searches
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ A Command is something that handles the input from a user and causes a result to
|
|||
An example is `look`, which examines your current location and tells how it looks like and
|
||||
what is in it.
|
||||
|
||||
```sidebar:: Commands are not typeclassed
|
||||
```{sidebar} Commands are not typeclassed
|
||||
|
||||
If you just came from the previous lesson, you might want to know that Commands and
|
||||
CommandSets are not `typeclassed`. That is, instances of them are not saved to the
|
||||
|
|
@ -181,7 +181,7 @@ class CmdEcho(Command):
|
|||
First we added a docstring. This is always a good thing to do in general, but for a Command class, it will also
|
||||
automatically become the in-game help entry! Next we add the `func` method. It has one active line where it
|
||||
makes use of some of those variables we found the Command offers to us. If you did the
|
||||
[basic Python tutorial](./Python-basic-introduction), you will recognize `.msg` - this will send a message
|
||||
[basic Python tutorial](./Python-basic-introduction.md), you will recognize `.msg` - this will send a message
|
||||
to the object it is attached to us - in this case `self.caller`, that is, us. We grab `self.args` and includes
|
||||
that in the message.
|
||||
|
||||
|
|
@ -306,7 +306,7 @@ A lot of things to dissect here:
|
|||
`self.args.strip()` over and over, we store the stripped version
|
||||
in a _local variable_ `args`. Note that we don't modify `self.args` by doing this, `self.args` will still
|
||||
have the whitespace and is not the same as `args` in this example.
|
||||
```sidebar:: if-statements
|
||||
```{sidebar} if-statements
|
||||
|
||||
The full form of the if statement is
|
||||
|
||||
|
|
@ -354,7 +354,7 @@ class MyCmdSet(CmdSet):
|
|||
|
||||
```
|
||||
|
||||
```sidebar:: Errors in your code
|
||||
```{sidebar} Errors in your code
|
||||
|
||||
With longer code snippets to try, it gets more and more likely you'll
|
||||
make an error and get a `traceback` when you reload. This will either appear
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
# Using the game and building stuff
|
||||
|
||||
In this lesson we will test out what we can do in-game out-of-the-box. Evennia ships with
|
||||
[around 90 default commands](api:evennia.commands.default#modules), and while you can override those as you please,
|
||||
In this lesson we will test out what we can do in-game out-of-the-box. Evennia ships with
|
||||
[around 90 default commands](../../../Components/Default-Commands.md), and while you can override those as you please,
|
||||
they can be quite useful.
|
||||
|
||||
Connect and log into your new game and you will end up in the "Limbo" location. This
|
||||
Connect and log into your new game and you will end up in the "Limbo" location. This
|
||||
is the only room in the game at this point. Let's explore the commands a little.
|
||||
|
||||
The default commands has syntax [similar to MUX](../../../Concepts/Using-MUX-as-a-Standard):
|
||||
The default commands has syntax [similar to MUX](../../../Concepts/Using-MUX-as-a-Standard.md):
|
||||
|
||||
command[/switch/switch...] [arguments ...]
|
||||
|
||||
An example would be
|
||||
An example would be
|
||||
|
||||
create/drop box
|
||||
|
||||
|
|
@ -21,32 +21,32 @@ or more inputs to the commands. It's common to use an equal sign (`=`) when assi
|
|||
an object.
|
||||
|
||||
> Are you used to commands starting with @, like @create? That will work too. Evennia simply ignores
|
||||
> the preceeding @.
|
||||
> the preceeding @.
|
||||
|
||||
## Getting help
|
||||
|
||||
help
|
||||
|
||||
Will give you a list of all commands available to you. Use
|
||||
help
|
||||
|
||||
Will give you a list of all commands available to you. Use
|
||||
|
||||
help <commandname>
|
||||
|
||||
to see the in-game help for that command.
|
||||
|
||||
to see the in-game help for that command.
|
||||
|
||||
## Looking around
|
||||
|
||||
The most common comman is
|
||||
The most common comman is
|
||||
|
||||
look
|
||||
|
||||
This will show you the description of the current location. `l` is an alias.
|
||||
|
||||
This will show you the description of the current location. `l` is an alias.
|
||||
|
||||
When targeting objects in commands you have two special labels you can use, `here` for the current
|
||||
room or `me`/`self` to point back to yourself. So
|
||||
room or `me`/`self` to point back to yourself. So
|
||||
|
||||
look me
|
||||
|
||||
will give you your own description. `look here` is, in this case, the same as plain `look`.
|
||||
will give you your own description. `look here` is, in this case, the same as plain `look`.
|
||||
|
||||
|
||||
## Stepping Down From Godhood
|
||||
|
|
@ -81,9 +81,9 @@ This created a new 'box' (of the default object type) in your inventory. Use the
|
|||
|
||||
name box = very large box;box;very;crate
|
||||
|
||||
```warning:: MUD clients and semi-colon
|
||||
```{warning} MUD clients and semi-colon
|
||||
|
||||
Some traditional MUD clients use the semi-colon `;` to separate client inputs. If so,
|
||||
Some traditional MUD clients use the semi-colon `;` to separate client inputs. If so,
|
||||
the above line will give an error. You need to change your client to use another command-separator
|
||||
or to put it in 'verbatim' mode. If you still have trouble, use the Evennia web client instead.
|
||||
|
||||
|
|
@ -99,8 +99,8 @@ used the `alias` command.
|
|||
|
||||
We are currently carrying the box. Let's drop it (there is also a short cut to create and drop in
|
||||
one go by using the `/drop` switch, for example `create/drop box`).
|
||||
|
||||
drop box
|
||||
|
||||
drop box
|
||||
|
||||
Hey presto - there it is on the ground, in all its normality.
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ Hey presto - there it is on the ground, in all its normality.
|
|||
This will show some technical details about the box object. For now we will ignore what this
|
||||
information means.
|
||||
|
||||
Try to `look` at the box to see the (default) description.
|
||||
Try to `look` at the box to see the (default) description.
|
||||
|
||||
look box
|
||||
You see nothing special.
|
||||
|
|
@ -125,20 +125,20 @@ dropped in the room, then try this:
|
|||
|
||||
lock box = get:false()
|
||||
|
||||
Locks represent a rather [big topic](../../../Components/Locks), but for now that will do what we want. This will lock
|
||||
Locks represent a rather [big topic](../../../Components/Locks.md), but for now that will do what we want. This will lock
|
||||
the box so noone can lift it. The exception is superusers, they override all locks and will pick it
|
||||
up anyway. Make sure you are quelling your superuser powers and try to get the box now:
|
||||
|
||||
> get box
|
||||
You can't get that.
|
||||
|
||||
Think thís default error message looks dull? The `get` command looks for an [Attribute](../../../Components/Attributes)
|
||||
Think thís default error message looks dull? The `get` command looks for an [Attribute](../../../Components/Attributes.md)
|
||||
named `get_err_msg` for returning a nicer error message (we just happen to know this, you would need
|
||||
to peek into the
|
||||
[code](https://github.com/evennia/evennia/blob/master/evennia/commands/default/general.py#L235) for
|
||||
the `get` command to find out.). You set attributes using the `set` command:
|
||||
|
||||
set box/get_err_msg = It's way too heavy for you to lift.
|
||||
set box/get_err_msg = It's way too heavy for you to lift.
|
||||
|
||||
Try to get it now and you should see a nicer error message echoed back to you. To see what this
|
||||
message string is in the future, you can use 'examine.'
|
||||
|
|
@ -149,12 +149,12 @@ Examine will return the value of attributes, including color codes. `examine her
|
|||
the raw description of your current room (including color codes), so that you can copy-and-paste to
|
||||
set its description to something else.
|
||||
|
||||
You create new Commands (or modify existing ones) in Python outside the game. We will get to that
|
||||
later, in the [Commands tutorial](./Adding-Commands).
|
||||
You create new Commands (or modify existing ones) in Python outside the game. We will get to that
|
||||
later, in the [Commands tutorial](./Adding-Commands.md).
|
||||
|
||||
## Get a Personality
|
||||
|
||||
[Scripts](../../../Components/Scripts) are powerful out-of-character objects useful for many "under the hood" things.
|
||||
[Scripts](../../../Components/Scripts.md) are powerful out-of-character objects useful for many "under the hood" things.
|
||||
One of their optional abilities is to do things on a timer. To try out a first script, let's put one
|
||||
on ourselves. There is an example script in `evennia/contrib/tutorial_examples/bodyfunctions.py`
|
||||
that is called `BodyFunctions`. To add this to us we will use the `script` command:
|
||||
|
|
@ -162,16 +162,16 @@ that is called `BodyFunctions`. To add this to us we will use the `script` comma
|
|||
script self = tutorial_examples.bodyfunctions.BodyFunctions
|
||||
|
||||
This string will tell Evennia to dig up the Python code at the place we indicate. It already knows
|
||||
to look in the `contrib/` folder, so we don't have to give the full path.
|
||||
to look in the `contrib/` folder, so we don't have to give the full path.
|
||||
|
||||
> Note also how we use `.` instead of `/` (or `\` on Windows). This is a so-called "Python path". In a Python-path,
|
||||
> Note also how we use `.` instead of `/` (or `\` on Windows). This is a so-called "Python path". In a Python-path,
|
||||
> you separate the parts of the path with `.` and skip the `.py` file-ending. Importantly, it also allows you to point to
|
||||
Python code _inside_ files, like the `BodyFunctions` class inside `bodyfunctions.py` (we'll get to classes later).
|
||||
These "Python-paths" are used extensively throughout Evennia.
|
||||
Python code _inside_ files, like the `BodyFunctions` class inside `bodyfunctions.py` (we'll get to classes later).
|
||||
These "Python-paths" are used extensively throughout Evennia.
|
||||
|
||||
Wait a while and you will notice yourself starting making random observations ...
|
||||
|
||||
script self
|
||||
script self
|
||||
|
||||
This will show details about scripts on yourself (also `examine` works). You will see how long it is
|
||||
until it "fires" next. Don't be alarmed if nothing happens when the countdown reaches zero - this
|
||||
|
|
@ -183,14 +183,14 @@ When you are tired of your character's "insights", kill the script with
|
|||
script/stop self = tutorial_examples.bodyfunctions.BodyFunctions
|
||||
|
||||
You create your own scripts in Python, outside the game; the path you give to `script` is literally
|
||||
the Python path to your script file. The [Scripts](../../../Components/Scripts) page explains more details.
|
||||
the Python path to your script file. The [Scripts](../../../Components/Scripts.md) page explains more details.
|
||||
|
||||
## Pushing Your Buttons
|
||||
|
||||
If we get back to the box we made, there is only so much fun you can have with it at this point. It's
|
||||
just a dumb generic object. If you renamed it to `stone` and changed its description, noone would be
|
||||
the wiser. However, with the combined use of custom [Typeclasses](../../../Components/Typeclasses), [Scripts](../../../Components/Scripts)
|
||||
and object-based [Commands](../../../Components/Commands), you could expand it and other items to be as unique, complex
|
||||
the wiser. However, with the combined use of custom [Typeclasses](../../../Components/Typeclasses.md), [Scripts](../../../Components/Scripts.md)
|
||||
and object-based [Commands](../../../Components/Commands.md), you could expand it and other items to be as unique, complex
|
||||
and interactive as you want.
|
||||
|
||||
Let's take an example. So far we have only created objects that use the default object typeclass
|
||||
|
|
@ -206,18 +206,18 @@ The same way we did with the Script Earler, we specify a "Python-path" to the Py
|
|||
to use for creating the object. There you go - one red button.
|
||||
|
||||
The RedButton is an example object intended to show off a few of Evennia's features. You will find
|
||||
that the [Typeclass](../../../Components/Typeclasses) and [Commands](../../../Components/Commands) controlling it are
|
||||
inside [evennia/contrib/tutorial_examples](api:evennia.contrib.tutorial_examples)
|
||||
that the [Typeclass](../../../Components/Typeclasses.md) and [Commands](../../../Components/Commands.md) controlling it are
|
||||
inside [evennia/contrib/tutorial_examples](../../../api/evennia.contrib.tutorial_examples.md)
|
||||
|
||||
If you wait for a while (make sure you dropped it!) the button will blink invitingly.
|
||||
If you wait for a while (make sure you dropped it!) the button will blink invitingly.
|
||||
|
||||
Why don't you try to push it ...?
|
||||
Why don't you try to push it ...?
|
||||
|
||||
Surely a big red button is meant to be pushed.
|
||||
Surely a big red button is meant to be pushed.
|
||||
|
||||
You know you want to.
|
||||
|
||||
```warning:: Don't press the invitingly blinking red button.
|
||||
```{warning} Don't press the invitingly blinking red button.
|
||||
```
|
||||
|
||||
## Making Yourself a House
|
||||
|
|
@ -242,14 +242,14 @@ also up/down and in/out). It's called `tunnel`:
|
|||
This will create a new room "cliff" with an exit "southwest" leading there and a path "northeast"
|
||||
leading back from the cliff to your current location.
|
||||
|
||||
You can create new exits from where you are, using the `open` command:
|
||||
You can create new exits from where you are, using the `open` command:
|
||||
|
||||
open north;n = house
|
||||
|
||||
This opens an exit `north` (with an alias `n`) to the previously created room `house`.
|
||||
|
||||
If you have many rooms named `house` you will get a list of matches and have to select which one you
|
||||
want to link to.
|
||||
want to link to.
|
||||
|
||||
Follow the north exit to your 'house' or `teleport` to it:
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ _large box_ to our house.
|
|||
very large box is leaving Limbo, heading for house.
|
||||
Teleported very large box -> house.
|
||||
|
||||
We can still find the box by using find:
|
||||
We can still find the box by using find:
|
||||
|
||||
find box
|
||||
One Match(#1-#8):
|
||||
|
|
@ -291,15 +291,15 @@ We are getting tired of the box. Let's destroy it.
|
|||
|
||||
destroy box
|
||||
|
||||
It will ask you for confirmation. Once you give it, the box will be gone.
|
||||
It will ask you for confirmation. Once you give it, the box will be gone.
|
||||
|
||||
You can destroy many objects in one go by giving a comma-separated list of objects (or a range
|
||||
of #dbrefs, if they are not in the same location) to the command.
|
||||
|
||||
## Adding a Help Entry
|
||||
|
||||
The Command-help is something you modify in Python code. We'll get to that when we get to how to
|
||||
add Commands. But you can also add regular help entries, for example to explain something about
|
||||
The Command-help is something you modify in Python code. We'll get to that when we get to how to
|
||||
add Commands. But you can also add regular help entries, for example to explain something about
|
||||
the history of your game world:
|
||||
|
||||
sethelp/add History = At the dawn of time ...
|
||||
|
|
@ -308,5 +308,5 @@ You will now find your new `History` entry in the `help` list and read your help
|
|||
|
||||
## Adding a World
|
||||
|
||||
After this brief introduction to building and using in-game commands you may be ready to see a more fleshed-out
|
||||
After this brief introduction to building and using in-game commands you may be ready to see a more fleshed-out
|
||||
example. Evennia comes with a tutorial world for you to explore. We will try that out in the next section.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ Given the path to a Typeclass, there are three ways to create an instance of it:
|
|||
This is the recommended way if you are trying to create things in Python. The first argument can either be
|
||||
the class _or_ the python-path to the typeclass, like `"path.to.SomeTypeClass"`. It can also be `None` in which
|
||||
case the Evennia default will be used. While all the creation methods
|
||||
are available on `evennia`, they are actually implemented in [evennia/utils/create.py](api:evennia.utils.create).
|
||||
are available on `evennia`, they are actually implemented in [evennia/utils/create.py](../../../api/evennia.utils.create.md).
|
||||
- Finally, you can create objects using an in-game command, such as
|
||||
|
||||
create/drop obj:path.to.SomeTypeClass
|
||||
|
|
|
|||
|
|
@ -1,88 +1,88 @@
|
|||
# Django Database queries
|
||||
|
||||
```important:: More advanced lesson!
|
||||
|
||||
Learning about Django's queryset language is very useful once you start doing more advanced things
|
||||
in Evennia. But it's not strictly needed out the box and can be a little overwhelming for a first
|
||||
reading. So if you are new to Python and Evennia, feel free to just skim this lesson and refer
|
||||
```{important} More advanced lesson!
|
||||
|
||||
Learning about Django's queryset language is very useful once you start doing more advanced things
|
||||
in Evennia. But it's not strictly needed out the box and can be a little overwhelming for a first
|
||||
reading. So if you are new to Python and Evennia, feel free to just skim this lesson and refer
|
||||
back to it later when you've gained more experience.
|
||||
```
|
||||
|
||||
The search functions and methods we used in the previous lesson are enough for most cases.
|
||||
But sometimes you need to be more specific:
|
||||
The search functions and methods we used in the previous lesson are enough for most cases.
|
||||
But sometimes you need to be more specific:
|
||||
|
||||
- You want to find all `Characters` ...
|
||||
- ... who are in Rooms tagged as `moonlit` ...
|
||||
- ... who are in Rooms tagged as `moonlit` ...
|
||||
- ... _and_ who has the Attribute `lycantrophy` with a level higher than 2 ...
|
||||
- ... because they'll should immediately transform to werewolves!
|
||||
- ... because they'll should immediately transform to werewolves!
|
||||
|
||||
In principle you could achieve this with the existing search functions combined with a lot of loops
|
||||
and if statements. But for something non-standard like this, querying the database directly will be
|
||||
In principle you could achieve this with the existing search functions combined with a lot of loops
|
||||
and if statements. But for something non-standard like this, querying the database directly will be
|
||||
much more efficient.
|
||||
|
||||
Evennia uses [Django](https://www.djangoproject.com/) to handle its connection to the database.
|
||||
A [django queryset](https://docs.djangoproject.com/en/3.0/ref/models/querysets/) represents
|
||||
a database query. One can add querysets together to build ever-more complicated queries. Only when
|
||||
you are trying to use the results of the queryset will it actually call the database.
|
||||
A [django queryset](https://docs.djangoproject.com/en/3.0/ref/models/querysets/) represents
|
||||
a database query. One can add querysets together to build ever-more complicated queries. Only when
|
||||
you are trying to use the results of the queryset will it actually call the database.
|
||||
|
||||
The normal way to build a queryset is to define what class of entity you want to search by getting its
|
||||
`.objects` resource, and then call various methods on that. We've seen this one before:
|
||||
The normal way to build a queryset is to define what class of entity you want to search by getting its
|
||||
`.objects` resource, and then call various methods on that. We've seen this one before:
|
||||
|
||||
all_weapons = Weapon.objects.all()
|
||||
|
||||
This is now a queryset representing all instances of `Weapon`. If `Weapon` had a subclass `Cannon` and we
|
||||
|
||||
This is now a queryset representing all instances of `Weapon`. If `Weapon` had a subclass `Cannon` and we
|
||||
only wanted the cannons, we would do
|
||||
|
||||
all_cannons = Cannon.objects.all()
|
||||
|
||||
Note that `Weapon` and `Cannon` are different typeclasses. You won't find any `Cannon` instances in
|
||||
the `all_weapon` result above, confusing as that may sound. To get instances of a Typeclass _and_ the
|
||||
Note that `Weapon` and `Cannon` are different typeclasses. You won't find any `Cannon` instances in
|
||||
the `all_weapon` result above, confusing as that may sound. To get instances of a Typeclass _and_ the
|
||||
instances of all its children classes you need to use `_family`:
|
||||
|
||||
```sidebar:: _family
|
||||
```{sidebar} _family
|
||||
|
||||
The all_family, filter_family etc is an Evennia-specific
|
||||
The all_family, filter_family etc is an Evennia-specific
|
||||
thing. It's not part of regular Django.
|
||||
|
||||
```
|
||||
|
||||
really_all_weapons = Weapon.objects.all_family()
|
||||
|
||||
This result now contains both `Weapon` and `Cannon` instances.
|
||||
|
||||
To limit your search by other criteria than the Typeclass you need to use `.filter`
|
||||
(or `.filter_family`) instead:
|
||||
This result now contains both `Weapon` and `Cannon` instances.
|
||||
|
||||
To limit your search by other criteria than the Typeclass you need to use `.filter`
|
||||
(or `.filter_family`) instead:
|
||||
|
||||
roses = Flower.objects.filter(db_key="rose")
|
||||
|
||||
This is a queryset representing all objects having a `db_key` equal to `"rose"`.
|
||||
|
||||
This is a queryset representing all objects having a `db_key` equal to `"rose"`.
|
||||
Since this is a queryset you can keep adding to it; this will act as an `AND` condition.
|
||||
|
||||
local_roses = roses.filter(db_location=myroom)
|
||||
|
||||
We could also have written this in one statement:
|
||||
|
||||
We could also have written this in one statement:
|
||||
|
||||
local_roses = Flower.objects.filter(db_key="rose", db_location=myroom)
|
||||
|
||||
|
||||
We can also `.exclude` something from results
|
||||
|
||||
local_non_red_roses = local_roses.exclude(db_key="red_rose")
|
||||
|
||||
Only until we actually try to examine the result will the database be called. Here it's called when we
|
||||
try to loop over the queryset:
|
||||
|
||||
Only until we actually try to examine the result will the database be called. Here it's called when we
|
||||
try to loop over the queryset:
|
||||
|
||||
for rose in local_non_red_roses:
|
||||
print(rose)
|
||||
|
||||
From now on, the queryset is _evaluated_ and we can't keep adding more queries to it - we'd need to
|
||||
|
||||
From now on, the queryset is _evaluated_ and we can't keep adding more queries to it - we'd need to
|
||||
create a new queryset if we wanted to find some other result. Other ways to evaluate the queryset is to
|
||||
print it, convert it to a list with `list()` and otherwise try to access its results.
|
||||
|
||||
|
||||
Note how we use `db_key` and `db_location`. This is the actual names of these database fields. By convention
|
||||
Evennia uses `db_` in front of every database field. When you use the normal Evennia search helpers and objects
|
||||
you can skip the `db_` but here we are calling the database directly and need to use the 'real' names.
|
||||
you can skip the `db_` but here we are calling the database directly and need to use the 'real' names.
|
||||
|
||||
Here are the most commonly used methods to use with the `objects` managers:
|
||||
Here are the most commonly used methods to use with the `objects` managers:
|
||||
|
||||
- `filter` - query for a listing of objects based on search criteria. Gives empty queryset if none
|
||||
were found.
|
||||
|
|
@ -91,71 +91,71 @@ found.
|
|||
- `all` - get all instances of the particular type.
|
||||
- `filter_family` - like `filter`, but search all sub classes as well.
|
||||
- `get_family` - like `get`, but search all sub classes as well.
|
||||
- `all_family` - like `all`, but return entities of all subclasses as well.
|
||||
- `all_family` - like `all`, but return entities of all subclasses as well.
|
||||
|
||||
> All of Evennia search functions use querysets under the hood. The `evennia.search_*` functions actually
|
||||
> All of Evennia search functions use querysets under the hood. The `evennia.search_*` functions actually
|
||||
> return querysets, which means you could in principle keep adding queries to their results as well.
|
||||
|
||||
|
||||
### Queryset field lookups
|
||||
## Queryset field lookups
|
||||
|
||||
Above we found roses with exactly the `db_key` `"rose"`. This is an _exact_ match that is _case sensitive_,
|
||||
so it would not find `"Rose"`.
|
||||
Above we found roses with exactly the `db_key` `"rose"`. This is an _exact_ match that is _case sensitive_,
|
||||
so it would not find `"Rose"`.
|
||||
|
||||
# this is case-sensitive and the same as =
|
||||
roses = Flower.objects.filter(db_key__exact="rose"
|
||||
|
||||
|
||||
# the i means it's case-insensitive
|
||||
roses = Flower.objects.filter(db_key__iexact="rose")
|
||||
|
||||
The Django field query language uses `__` in the same way as Python uses `.` to access resources. This
|
||||
is because `.` is not allowed in a function keyword.
|
||||
|
||||
The Django field query language uses `__` in the same way as Python uses `.` to access resources. This
|
||||
is because `.` is not allowed in a function keyword.
|
||||
|
||||
roses = Flower.objects.filter(db_key__icontains="rose")
|
||||
|
||||
This will find all flowers whose name contains the string `"rose"`, like `"roses"`, `"wild rose"` etc. The
|
||||
`i` in the beginning makes the search case-insensitive. Other useful variations to use
|
||||
are `__istartswith` and `__iendswith`. You can also use `__gt`, `__ge` for "greater-than"/"greater-or-equal-than"
|
||||
|
||||
This will find all flowers whose name contains the string `"rose"`, like `"roses"`, `"wild rose"` etc. The
|
||||
`i` in the beginning makes the search case-insensitive. Other useful variations to use
|
||||
are `__istartswith` and `__iendswith`. You can also use `__gt`, `__ge` for "greater-than"/"greater-or-equal-than"
|
||||
comparisons (same for `__lt` and `__le`). There is also `__in`:
|
||||
|
||||
swords = Weapons.objects.filter(db_key__in=("rapier", "two-hander", "shortsword"))
|
||||
|
||||
|
||||
One also uses `__` to access foreign objects like Tags. Let's for example assume this is how we identify mages:
|
||||
|
||||
char.tags.add("mage", category="profession")
|
||||
|
||||
Now, in this case we have an Evennia helper to do this search:
|
||||
Now, in this case we have an Evennia helper to do this search:
|
||||
|
||||
mages = evennia.search_tags("mage", category="profession")
|
||||
|
||||
But this will find all Objects with this tag+category. Maybe you are only looking for Vampire mages:
|
||||
|
||||
sparkly_mages = Vampire.objects.filter(db_tags__db_key="mage", db_tags__db_category="profession")
|
||||
|
||||
This looks at the `db_tags` field on the `Vampire` and filters on the values of each tag's
|
||||
|
||||
This looks at the `db_tags` field on the `Vampire` and filters on the values of each tag's
|
||||
`db_key` and `db_category` together.
|
||||
|
||||
For more field lookups, see the
|
||||
|
||||
For more field lookups, see the
|
||||
[django docs](https://docs.djangoproject.com/en/3.0/ref/models/querysets/#field-lookups) on the subject.
|
||||
|
||||
### Get that werewolf ...
|
||||
## Get that werewolf ...
|
||||
|
||||
Let's see if we can make a query for the werewolves in the moonlight we mentioned at the beginning
|
||||
of this section.
|
||||
Let's see if we can make a query for the werewolves in the moonlight we mentioned at the beginning
|
||||
of this section.
|
||||
|
||||
Firstly, we make ourselves and our current location match the criteria, so we can test:
|
||||
Firstly, we make ourselves and our current location match the criteria, so we can test:
|
||||
|
||||
> py here.tags.add("moonlit")
|
||||
> py me.db.lycantrophy = 3
|
||||
|
||||
This is an example of a more complex query. We'll consider it an example of what is
|
||||
|
||||
This is an example of a more complex query. We'll consider it an example of what is
|
||||
possible.
|
||||
|
||||
```sidebar:: Line breaks
|
||||
```{sidebar} Line breaks
|
||||
|
||||
Note the way of writing this code. It would have been very hard to read if we just wrote it in
|
||||
Note the way of writing this code. It would have been very hard to read if we just wrote it in
|
||||
one long line. But since we wrapped it in `(...)` we can spread it out over multiple lines
|
||||
without worrying about line breaks!
|
||||
without worrying about line breaks!
|
||||
```
|
||||
|
||||
```python
|
||||
|
|
@ -172,30 +172,30 @@ will_transform = (
|
|||
|
||||
- **Line 3** - We want to find `Character`s, so we access `.objects` on the `Character` typeclass.
|
||||
- **Line 4** - We start to filter ...
|
||||
- **Line 5**
|
||||
- **Line 5**
|
||||
- ... by accessing the `db_location` field (usually this is a Room)
|
||||
- ... and on that location, we get the value of `db_tags` (this is a _many-to-many_ database field
|
||||
that we can treat like an object for this purpose; it references all Tags on the location)
|
||||
that we can treat like an object for this purpose; it references all Tags on the location)
|
||||
- ... and from those `Tags`, we looking for `Tags` whose `db_key` is "monlit" (non-case sensitive).
|
||||
- **Line 6** - ... We also want only Characters with `Attributes` whose `db_key` is exactly `"lycantrophy"`
|
||||
- **Line 7** - ... at the same time as the `Attribute`'s `db_value` is greater-than 2.
|
||||
|
||||
Running this query makes our newly lycantrrophic Character appear in `will_transform`. Success!
|
||||
- **Line 7** - ... at the same time as the `Attribute`'s `db_value` is greater-than 2.
|
||||
|
||||
> Don't confuse database fields with [Attributes](../../../Components/Attributes) you set via `obj.db.attr = 'foo'` or
|
||||
Running this query makes our newly lycantrrophic Character appear in `will_transform`. Success!
|
||||
|
||||
> Don't confuse database fields with [Attributes](../../../Components/Attributes.md) you set via `obj.db.attr = 'foo'` or
|
||||
`obj.attributes.add()`. Attributes are custom database entities *linked* to an object. They are not
|
||||
separate fields *on* that object like `db_key` or `db_location` are.
|
||||
separate fields *on* that object like `db_key` or `db_location` are.
|
||||
|
||||
### Complex queries
|
||||
## Complex queries
|
||||
|
||||
All examples so far used `AND` relations. The arguments to `.filter` are added together with `AND`
|
||||
("we want tag room to be "monlit" _and_ lycantrhopy be > 2").
|
||||
|
||||
For queries using `OR` and `NOT` we need Django's
|
||||
[Q object](https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q-objects). It is
|
||||
imported from Django directly:
|
||||
For queries using `OR` and `NOT` we need Django's
|
||||
[Q object](https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q-objects). It is
|
||||
imported from Django directly:
|
||||
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q
|
||||
|
||||
The `Q` is an object that is created with the same arguments as `.filter`, for example
|
||||
|
||||
|
|
@ -205,28 +205,28 @@ You can then use this `Q` instance as argument in a `filter`:
|
|||
|
||||
q1 = Q(db_key="foo")
|
||||
Character.objects.filter(q1)
|
||||
|
||||
|
||||
The useful thing about `Q` is that these objects can be chained together with special symbols (bit operators):
|
||||
`|` for `OR` and `&` for `AND`. A tilde `~` in front negates the expression inside the `Q` and thus
|
||||
works like `NOT`.
|
||||
|
||||
The useful thing about `Q` is that these objects can be chained together with special symbols (bit operators):
|
||||
`|` for `OR` and `&` for `AND`. A tilde `~` in front negates the expression inside the `Q` and thus
|
||||
works like `NOT`.
|
||||
|
||||
q1 = Q(db_key="Dalton")
|
||||
q2 = Q(db_location=prison)
|
||||
Character.objects.filter(q1 | ~q2)
|
||||
|
||||
|
||||
Would get all Characters that are either named "Dalton" _or_ which is _not_ in prison. The result is a mix
|
||||
of Daltons and non-prisoners.
|
||||
of Daltons and non-prisoners.
|
||||
|
||||
Let us expand our original werewolf query. Not only do we want to find all Characters in a moonlit room
|
||||
with a certain level of `lycanthrophy`. Now we also want the full moon to immediately transform people who were
|
||||
with a certain level of `lycanthrophy`. Now we also want the full moon to immediately transform people who were
|
||||
recently bitten, even if their `lycantrophy` level is not yet high enough (more dramatic this way!). Let's say there is
|
||||
a Tag "recently_bitten" that controls this.
|
||||
|
||||
This is how we'd change our query:
|
||||
This is how we'd change our query:
|
||||
|
||||
```python
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q
|
||||
|
||||
will_transform = (
|
||||
Character.objects
|
||||
|
|
@ -244,7 +244,7 @@ will_transform = (
|
|||
That's quite compact. It may be easier to see what's going on if written this way:
|
||||
|
||||
```python
|
||||
from django.db.models import Q
|
||||
from django.db.models import Q
|
||||
|
||||
q_moonlit = Q(db_location__db_tags__db_key__iexact="moonlit")
|
||||
q_lycantropic = Q(db_attributes__db_key="lycantrophy", db_attributes__db_value__gt=2)
|
||||
|
|
@ -257,7 +257,7 @@ will_transform = (
|
|||
)
|
||||
```
|
||||
|
||||
```sidebar:: SQL
|
||||
```{sidebar} SQL
|
||||
|
||||
These Python structures are internally converted to SQL, the native language of the database.
|
||||
If you are familiar with SQL, these are many-to-many tables joined with `LEFT OUTER JOIN`,
|
||||
|
|
@ -266,31 +266,31 @@ will_transform = (
|
|||
```
|
||||
|
||||
This reads as "Find all Characters in a moonlit room that either has the Attribute `lycantrophy` higher
|
||||
than two _or_ which has the Tag `recently_bitten`". With an OR-query like this it's possible to find the
|
||||
same Character via different paths, so we add `.distinct()` at the end. This makes sure that there is only
|
||||
than two _or_ which has the Tag `recently_bitten`". With an OR-query like this it's possible to find the
|
||||
same Character via different paths, so we add `.distinct()` at the end. This makes sure that there is only
|
||||
one instance of each Character in the result.
|
||||
|
||||
### Annotations
|
||||
## Annotations
|
||||
|
||||
What if we wanted to filter on some condition that isn't represented easily by a field on the
|
||||
object? Maybe we want to find rooms only containing five or more objects?
|
||||
|
||||
We *could* do it like this (don't actually do it this way!):
|
||||
We *could* do it like this (don't actually do it this way!):
|
||||
|
||||
```python
|
||||
from typeclasses.rooms import Room
|
||||
|
||||
all_rooms = Rooms.objects.all()
|
||||
all_rooms = Rooms.objects.all()
|
||||
|
||||
rooms_with_five_objects = []
|
||||
for room in all_rooms:
|
||||
for room in all_rooms:
|
||||
if len(room.contents) >= 5:
|
||||
rooms_with_five_objects.append(room)
|
||||
```
|
||||
|
||||
Above we get all rooms and then use `list.append()` to keep adding the right rooms
|
||||
to an ever-growing list. This is _not_ a good idea, once your database grows this will
|
||||
be unnecessarily computing-intensive. The database is much more suitable for this.
|
||||
Above we get all rooms and then use `list.append()` to keep adding the right rooms
|
||||
to an ever-growing list. This is _not_ a good idea, once your database grows this will
|
||||
be unnecessarily computing-intensive. The database is much more suitable for this.
|
||||
|
||||
_Annotations_ allow you to set a 'variable' inside the query that you can
|
||||
then access from other parts of the query. Let's do the same example as before directly in the database:
|
||||
|
|
@ -307,10 +307,10 @@ rooms = (
|
|||
)
|
||||
```
|
||||
|
||||
`Count` is a Django class for counting the number of things in the database.
|
||||
`Count` is a Django class for counting the number of things in the database.
|
||||
|
||||
Here we first create an annotation `num_objects` of type `Count`. It creates an in-database function
|
||||
that will count the number of results inside the database.
|
||||
that will count the number of results inside the database.
|
||||
|
||||
> Note the use of `location_set` in that `Count`. The `*_set` is a back-reference automatically created by
|
||||
Django. In this case it allows you to find all objects that *has the current object as location*.
|
||||
|
|
@ -319,13 +319,13 @@ Next we filter on this annotation, using the name `num_objects` as something we
|
|||
use `num_objects__gte=5` which means that `num_objects` should be greater than 5. This is a little
|
||||
harder to get one's head around but much more efficient than lopping over all objects in Python.
|
||||
|
||||
### F-objects
|
||||
## F-objects
|
||||
|
||||
What if we wanted to compare two dynamic parameters against one another in a query? For example, what if
|
||||
instead of having 5 or more objects, we only wanted objects that had a bigger inventory than they had
|
||||
tags (silly example, but ...)? This can be with Django's
|
||||
instead of having 5 or more objects, we only wanted objects that had a bigger inventory than they had
|
||||
tags (silly example, but ...)? This can be with Django's
|
||||
[F objects](https://docs.djangoproject.com/en/1.11/ref/models/expressions/#f-expressions).
|
||||
So-called F expressions allow you to do a query that looks at a value of each object in the database.
|
||||
So-called F expressions allow you to do a query that looks at a value of each object in the database.
|
||||
|
||||
```python
|
||||
from django.db.models import Count, F
|
||||
|
|
@ -334,24 +334,24 @@ from typeclasses.rooms import Room
|
|||
result = (
|
||||
Room.objects
|
||||
.annotate(
|
||||
num_objects=Count('locations_set'),
|
||||
num_objects=Count('locations_set'),
|
||||
num_tags=Count('db_tags'))
|
||||
.filter(num_objects__gt=F('num_tags'))
|
||||
)
|
||||
```
|
||||
|
||||
Here we used `.annotate` to create two in-query 'variables' `num_objects` and `num_tags`. We then
|
||||
Here we used `.annotate` to create two in-query 'variables' `num_objects` and `num_tags`. We then
|
||||
directly use these results in the filter. Using `F()` allows for also the right-hand-side of the filter
|
||||
condition to be calculated on the fly, completely within the database.
|
||||
|
||||
### Grouping and returning only certain properties
|
||||
## Grouping and returning only certain properties
|
||||
|
||||
Suppose you used tags to mark someone belonging to an organization. Now you want to make a list and
|
||||
need to get the membership count of every organization all at once.
|
||||
need to get the membership count of every organization all at once.
|
||||
|
||||
The `.annotate`, `.values_list`, and `.order_by` queryset methods are useful for this. Normally when
|
||||
you run a `.filter`, what you get back is a bunch of full typeclass instances, like roses or swords.
|
||||
Using `.values_list` you can instead choose to only get back certain properties on objects.
|
||||
The `.annotate`, `.values_list`, and `.order_by` queryset methods are useful for this. Normally when
|
||||
you run a `.filter`, what you get back is a bunch of full typeclass instances, like roses or swords.
|
||||
Using `.values_list` you can instead choose to only get back certain properties on objects.
|
||||
The `.order_by` method finally allows for sorting the results according to some criterion:
|
||||
|
||||
|
||||
|
|
@ -372,7 +372,7 @@ Here we fetch all Characters who ...
|
|||
- ... along the way we count how many different Characters (each `id` is unique) we find for each organization
|
||||
and store it in a 'variable' `tagcount` using `.annotate` and `Count`
|
||||
- ... we use this count to sort the result in descending order of `tagcount` (descending because there is a minus sign,
|
||||
default is increasing order but we want the most popular organization to be first).
|
||||
default is increasing order but we want the most popular organization to be first).
|
||||
- ... and finally we make sure to only return exactly the properties we want, namely the name of the organization tag
|
||||
and how many matches we found for that organization.
|
||||
|
||||
|
|
@ -380,18 +380,18 @@ The result queryset will be a list of tuples ordered in descending order by the
|
|||
in a format like the following:
|
||||
```
|
||||
[
|
||||
('Griatch's poets society', 3872),
|
||||
("Chainsol's Ainneve Testers", 2076),
|
||||
('Griatch's poets society', 3872),
|
||||
("Chainsol's Ainneve Testers", 2076),
|
||||
("Blaufeuer's Whitespace Fixers", 1903),
|
||||
("Volund's Bikeshed Design Crew", 1764),
|
||||
("Tehom's Glorious Misanthropes", 1763)
|
||||
]
|
||||
```
|
||||
|
||||
## Conclusions
|
||||
## Conclusions
|
||||
|
||||
We have covered a lot of ground in this lesson and covered several more complex topics. Knowing how to
|
||||
query using Django is a powerful skill to have.
|
||||
We have covered a lot of ground in this lesson and covered several more complex topics. Knowing how to
|
||||
query using Django is a powerful skill to have.
|
||||
|
||||
This concludes the first part of the Evennia starting tutorial - "What we have". Now we have a good foundation
|
||||
to understand how to plan what our tutorial game will be about.
|
||||
|
|
|
|||
|
|
@ -1,126 +1,123 @@
|
|||
# Overview of the Evennia library
|
||||
|
||||
```sidebar:: API
|
||||
```{sidebar} API
|
||||
|
||||
API stands for `Application Programming Interface`, a description for how to access
|
||||
the resources of a program or library.
|
||||
```
|
||||
A good place to start exploring Evennia is the [Evenia-API frontpage](../../../Evennia-API).
|
||||
This page sums up the main components of Evennia with a short description of each. Try clicking through
|
||||
A good place to start exploring Evennia is the [Evenia-API frontpage](../../../Evennia-API.md).
|
||||
This page sums up the main components of Evennia with a short description of each. Try clicking through
|
||||
to a few entries - once you get deep enough you'll see full descriptions
|
||||
of each component along with their documentation. You can also click `[source]` to see the full Python source
|
||||
for each thing.
|
||||
of each component along with their documentation. You can also click `[source]` to see the full Python source
|
||||
for each thing.
|
||||
|
||||
You can also browse [the evennia repository on github](https://github.com/evennia/evennia). This is exactly
|
||||
what you can download from us. The github repo is also searchable.
|
||||
|
||||
Finally, you can clone the evennia repo to your own computer and read the sources locally. This is necessary
|
||||
if you want to help with Evennia's development itself. See the
|
||||
[extended install instructions](../../../Setup/Extended-Installation) if you want to do this.
|
||||
|
||||
### Where is it?
|
||||
|
||||
If Evennia is installed, you can import from it simply with
|
||||
what you can download from us. The github repo is also searchable.
|
||||
|
||||
Finally, you can clone the evennia repo to your own computer and read the sources locally. This is necessary
|
||||
if you want to help with Evennia's development itself. See the
|
||||
[extended install instructions](../../../Setup/Extended-Installation.md) if you want to do this.
|
||||
|
||||
## Where is it?
|
||||
|
||||
If Evennia is installed, you can import from it simply with
|
||||
|
||||
import evennia
|
||||
from evennia import some_module
|
||||
from evennia.some_module.other_module import SomeClass
|
||||
|
||||
and so on.
|
||||
|
||||
from evennia.some_module.other_module import SomeClass
|
||||
|
||||
and so on.
|
||||
|
||||
If you installed Evennia with `pip install`, the library folder will be installed deep inside your Python
|
||||
installation. If you cloned the repo there will be a folder `evennia` on your hard drive there.
|
||||
installation. If you cloned the repo there will be a folder `evennia` on your hard drive there.
|
||||
|
||||
If you cloned the repo or read the code on `github` you'll find this being the outermost structure:
|
||||
|
||||
evennia/
|
||||
bin/
|
||||
evennia/
|
||||
bin/
|
||||
CHANGELOG.md
|
||||
...
|
||||
...
|
||||
docs/
|
||||
evennia/
|
||||
evennia/
|
||||
|
||||
This outer layer is for Evennia's installation and package distribution. That internal folder `evennia/evennia/` is
|
||||
This outer layer is for Evennia's installation and package distribution. That internal folder `evennia/evennia/` is
|
||||
the _actual_ library, the thing covered by the API auto-docs and what you get when you do `import evennia`.
|
||||
|
||||
> The `evennia/docs/` folder contains the sources for this documentation. See
|
||||
> [contributing to the docs](../../../Contributing-Docs) if you want to learn more about how this works.
|
||||
> The `evennia/docs/` folder contains the sources for this documentation. See
|
||||
> [contributing to the docs](../../../Contributing-Docs.md) if you want to learn more about how this works.
|
||||
|
||||
This the the structure of the Evennia library:
|
||||
|
||||
- evennia
|
||||
- [`__init__.py`](../../../Evennia-API#shortcuts) - The "flat API" of Evennia resides here.
|
||||
- [`settings_default.py`](../../../Components/Server-Conf#Settings-file) - Root settings of Evennia. Copy settings
|
||||
- [`__init__.py`](../../../Evennia-API.md#shortcuts) - The "flat API" of Evennia resides here.
|
||||
- [`settings_default.py`](../../../Setup/Server-Conf.md#settings-file) - Root settings of Evennia. Copy settings
|
||||
from here to `mygame/server/settings.py` file.
|
||||
- [`commands/`](../../../Components/Commands) - The command parser and handler.
|
||||
- `default/` - The [default commands](api:evennia.commands.default#modules) and cmdsets.
|
||||
- [`comms/`](../../../Components/Communications) - Systems for communicating in-game.
|
||||
- [`commands/`](../../../Components/Commands.md) - The command parser and handler.
|
||||
- `default/` - The [default commands](../../../Components/Default-Commands.md) and cmdsets.
|
||||
- [`comms/`](../../../Components/Communications.md) - Systems for communicating in-game.
|
||||
- `contrib/` - Optional plugins too game-specific for core Evennia.
|
||||
- `game_template/` - Copied to become the "game directory" when using `evennia --init`.
|
||||
- [`help/`](../../../Components/Help-System) - Handles the storage and creation of help entries.
|
||||
- `locale/` - Language files ([i18n](../../../Concepts/Internationalization)).
|
||||
- [`locks/`](../../../Components/Locks) - Lock system for restricting access to in-game entities.
|
||||
- [`objects/`](../../../Components/Objects) - In-game entities (all types of items and Characters).
|
||||
- [`prototypes/`](../../../Components/Prototypes) - Object Prototype/spawning system and OLC menu
|
||||
- [`accounts/`](../../../Components/Accounts) - Out-of-game Session-controlled entities (accounts, bots etc)
|
||||
- [`scripts/`](../../../Components/Scripts) - Out-of-game entities equivalence to Objects, also with timer support.
|
||||
- [`server/`](../../../Components/Portal-And-Server) - Core server code and Session handling.
|
||||
- `game_template/` - Copied to become the "game directory" when using `evennia --init`.
|
||||
- [`help/`](../../../Components/Help-System.md) - Handles the storage and creation of help entries.
|
||||
- `locale/` - Language files ([i18n](../../../Concepts/Internationalization.md)).
|
||||
- [`locks/`](../../../Components/Locks.md) - Lock system for restricting access to in-game entities.
|
||||
- [`objects/`](../../../Components/Objects.md) - In-game entities (all types of items and Characters).
|
||||
- [`prototypes/`](../../../Components/Prototypes.md) - Object Prototype/spawning system and OLC menu
|
||||
- [`accounts/`](../../../Components/Accounts.md) - Out-of-game Session-controlled entities (accounts, bots etc)
|
||||
- [`scripts/`](../../../Components/Scripts.md) - Out-of-game entities equivalence to Objects, also with timer support.
|
||||
- [`server/`](../../../Components/Portal-And-Server.md) - Core server code and Session handling.
|
||||
- `portal/` - Portal proxy and connection protocols.
|
||||
- [`typeclasses/`](../../../Components/Typeclasses) - Abstract classes for the typeclass storage and database system.
|
||||
- [`utils/`](../../../Components/Coding-Utils) - Various miscellaneous useful coding resources.
|
||||
- [`web/`](../../../Concepts/Web-Features) - Web resources and webserver. Partly copied into game directory on initialization.
|
||||
- [`typeclasses/`](../../../Components/Typeclasses.md) - Abstract classes for the typeclass storage and database system.
|
||||
- [`utils/`](../../../Components/Coding-Utils.md) - Various miscellaneous useful coding resources.
|
||||
- [`web/`](../../../Concepts/Web-Features.md) - Web resources and webserver. Partly copied into game directory on initialization.
|
||||
|
||||
```sidebar:: __init__.py
|
||||
```{sidebar} __init__.py
|
||||
|
||||
The `__init__.py` file is a special Python filename used to represent a Python 'package'.
|
||||
When you import `evennia` on its own, you import this file. When you do `evennia.foo` Python will
|
||||
first look for a property `.foo` in `__init__.py` and then for a module or folder of that name
|
||||
in the same location.
|
||||
first look for a property `.foo` in `__init__.py` and then for a module or folder of that name
|
||||
in the same location.
|
||||
|
||||
```
|
||||
|
||||
While all the actual Evennia code is found in the various folders, the `__init__.py` represents the entire
|
||||
While all the actual Evennia code is found in the various folders, the `__init__.py` represents the entire
|
||||
package `evennia`. It contains "shortcuts" to code that is actually located elsewhere. Most of these shortcuts
|
||||
are listed if you [scroll down a bit](../../../Evennia-API) on the Evennia-API page.
|
||||
are listed if you [scroll down a bit](../../../Evennia-API.md) on the Evennia-API page.
|
||||
|
||||
## An example of exploring the library
|
||||
|
||||
In the previous lesson we took a brief look at `mygame/typeclasses/objects` as an example of a Python module. Let's
|
||||
open it again. Inside is the `Object` class, which inherits from `DefaultObject`.
|
||||
open it again. Inside is the `Object` class, which inherits from `DefaultObject`.
|
||||
Near the top of the module is this line:
|
||||
|
||||
|
||||
from evennia import DefaultObject
|
||||
|
||||
We want to figure out just what this DefaultObject offers. Since this is imported directly from `evennia`, we
|
||||
are actually importing from `evennia/__init__.py`.
|
||||
We want to figure out just what this DefaultObject offers. Since this is imported directly from `evennia`, we
|
||||
are actually importing from `evennia/__init__.py`.
|
||||
|
||||
[Look at Line 189](evennia/__init__.py#L189) of `evennia/__init__.py` and you'll find this line:
|
||||
[Look at Line 159](github:evennia/__init__.py#159) of `evennia/__init__.py` and you'll find this line:
|
||||
|
||||
from .objects.objects import DefaultObject
|
||||
from .objects.objects import DefaultObject
|
||||
|
||||
```sidebar:: Relative and absolute imports
|
||||
```{sidebar} Relative and absolute imports
|
||||
|
||||
The first full-stop in `from .objects.objects ...` means that
|
||||
The first full-stop in `from .objects.objects ...` means that
|
||||
we are importing from the current location. This is called a `relative import`.
|
||||
By comparison, `from evennia.objects.objects` is an `absolute import`. In this particular
|
||||
case, the two would give the same result.
|
||||
case, the two would give the same result.
|
||||
```
|
||||
|
||||
> You can also look at [the right section of the API frontpage](../../../Evennia-API#typeclasses) and click through
|
||||
> You can also look at [the right section of the API frontpage](../../../Evennia-API.md#typeclasses) and click through
|
||||
> to the code that way.
|
||||
|
||||
The fact that `DefaultObject` is imported into `__init__.py` here is what makes it possible to also import
|
||||
The fact that `DefaultObject` is imported into `__init__.py` here is what makes it possible to also import
|
||||
it as `from evennia import DefaultObject` even though the code for the class is not actually here.
|
||||
|
||||
So to find the code for `DefaultObject` we need to look in `evennia/objects/objects.py`. Here's how
|
||||
to look it up in the docs:
|
||||
|
||||
1. Open the [API frontpage](../../../Evennia-API)
|
||||
2. Locate the link to [evennia.objects](api:evennia.objects) and click on it.
|
||||
3. Click through to [evennia.objects.objects](api:evennia.objects.objects).
|
||||
4. You are now in the python module. Scroll down (or search in your web browser) to find the `DefaultObject` class.
|
||||
5. You can now read what this does and what methods are on it. If you want to see the full source, click the
|
||||
\[[source](src:evennia.objects.objects#DefaultObject)\] link.
|
||||
|
||||
So to find the code for `DefaultObject` we need to look in `evennia/objects/objects.py`. Here's how
|
||||
to look it up in the docs:
|
||||
|
||||
1. Open the [API frontpage](../../../Evennia-API.md)
|
||||
2. Locate the link to [evennia.objects.objects](evennia.objects.objects) and click on it.
|
||||
3 You are now in the python module. Scroll down (or search in your web browser) to find the `DefaultObject` class.
|
||||
4 You can now read what this does and what methods are on it. If you want to see the full source, click the
|
||||
\[source\] link next to it.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Like everywhere in the docs we'll assume it's called `mygame`.
|
|||
You may have noticed when we were building things in-game that we would often refer to code through
|
||||
"python paths", such as
|
||||
|
||||
```sidebar:: Python-paths
|
||||
```{sidebar} Python-paths
|
||||
|
||||
A 'python path' uses '.' instead of '/' or '`\\`' and
|
||||
skips the `.py` ending of files. It can also point to
|
||||
|
|
@ -57,10 +57,10 @@ and how you point to it correctly.
|
|||
|
||||
## commands/
|
||||
|
||||
The `commands/` folder holds Python modules related to creating and extending the [Commands](../../../Components/Commands)
|
||||
The `commands/` folder holds Python modules related to creating and extending the [Commands](../../../Components/Commands.md)
|
||||
of Evennia. These manifest in game like the server understanding input like `look` or `dig`.
|
||||
|
||||
```sidebar:: Classes
|
||||
```{sidebar} Classes
|
||||
|
||||
A `class` is template for creating object-instances of a particular type
|
||||
in Python. We will explain classes in more detail in the next
|
||||
|
|
@ -149,28 +149,28 @@ knows where they are and will read them to configure itself at startup.
|
|||
|
||||
### typeclasses/
|
||||
|
||||
The [Typeclasses](../../../Components/Typeclasses) of Evennia are Evennia-specific Python classes whose instances save themselves
|
||||
The [Typeclasses](../../../Components/Typeclasses.md) of Evennia are Evennia-specific Python classes whose instances save themselves
|
||||
to the database. This allows a Character to remain in the same place and your updated strength stat to still
|
||||
be the same after a server reboot.
|
||||
|
||||
- [accounts.py](github:evennia/game_template/typeclasses/accounts.py) (Python-path: `typeclasses.accounts`) - An
|
||||
[Account](../../../Components/Accounts) represents the player connecting to the game. It holds information like email,
|
||||
[Account](../../../Components/Accounts.md) represents the player connecting to the game. It holds information like email,
|
||||
password and other out-of-character details.
|
||||
- [channels.py](github:evennia/game_template/typeclasses/channels.py) (Python-path: `typeclasses.channels`) -
|
||||
[Channels](../../../Components/Channels) are used to manage in-game communication between players.
|
||||
[Channels](../../../Components/Channels.md) are used to manage in-game communication between players.
|
||||
- [objects.py](github:evennia/game_template/typeclasses/objects.py) (Python-path: `typeclasses.objects`) -
|
||||
[Objects](../../../Components/Objects) represent all things having a location within the game world.
|
||||
[Objects](../../../Components/Objects.md) represent all things having a location within the game world.
|
||||
- [characters.py](github:evennia/game_template/typeclasses/characters.py) (Python-path: `typeclasses.characters`) -
|
||||
The [Character](../../../Components/Objects#Characers) is a subclass of Objects, controlled by Accounts - they are the player's
|
||||
The [Character](../../../Components/Objects.md#characters) is a subclass of Objects, controlled by Accounts - they are the player's
|
||||
avatars in the game world.
|
||||
- [rooms.py](github:evennia/game_template/typeclasses/rooms.py) (Python-path: `typeclasses.rooms`) - A
|
||||
[Room](../../../Components/Objects#Room) is also a subclass of Object; describing discrete locations. While the traditional
|
||||
[Room](../../../Components/Objects.md#rooms) is also a subclass of Object; describing discrete locations. While the traditional
|
||||
term is 'room', such a location can be anything and on any scale that fits your game, from a forest glade,
|
||||
an entire planet or an actual dungeon room.
|
||||
- [exits.py](github:evennia/game_template/typeclasses/exits.py) (Python-path: `typeclasses.exits`) -
|
||||
[Exits](../../../Components/Objects#Exit) is another subclass of Object. Exits link one Room to another.
|
||||
[Exits](../../../Components/Objects.md#exits) is another subclass of Object. Exits link one Room to another.
|
||||
- [scripts.py](github:evennia/game_template/typeclasses/scripts.py) (Python-path: `typeclasses.scripts`) -
|
||||
[Scripts](../../../Components/Scripts) are 'out-of-character' objects. They have no location in-game and can serve as basis for
|
||||
[Scripts](../../../Components/Scripts.md) are 'out-of-character' objects. They have no location in-game and can serve as basis for
|
||||
anything that needs database persistence, such as combat, weather, or economic systems. They also
|
||||
have the ability to execute code repeatedly, on a timer.
|
||||
|
||||
|
|
@ -200,8 +200,8 @@ people change and re-structure this in various ways to better fit their ideas.
|
|||
|
||||
- [batch_cmds.ev](github:evennia/game_template/world/batch_cmds.ev) - This is an `.ev` file, which is essentially
|
||||
just a list of Evennia commands to execute in sequence. This one is empty and ready to expand on. The
|
||||
[Tutorial World](./Tutorial-World-Introduction) was built with such a batch-file.
|
||||
- [prototypes.py](github:evennia/game_template/world/prototypes.py) - A [prototype](../../../Components/Prototypes) is a way
|
||||
[Tutorial World](./Tutorial-World-Introduction.md) was built with such a batch-file.
|
||||
- [prototypes.py](github:evennia/game_template/world/prototypes.py) - A [prototype](../../../Components/Prototypes.md) is a way
|
||||
to easily vary objects without changing their base typeclass. For example, one could use prototypes to
|
||||
tell that Two goblins, while both of the class 'Goblin' (so they follow the same code logic), should have different
|
||||
equipment, stats and looks.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Now that we have learned a little about how to find things in the Evennia library, let's use it.
|
||||
|
||||
In the [Python classes and objects](./Python-classes-and-objects) lesson we created the dragons Fluffy, Cuddly
|
||||
In the [Python classes and objects](./Python-classes-and-objects.md) lesson we created the dragons Fluffy, Cuddly
|
||||
and Smaug and made them fly and breathe fire. So far our dragons are short-lived - whenever we `restart`
|
||||
the server or `quit()` out of python mode they are gone.
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ If we knew what kind of methods and resources were available on `DefaultObject`
|
|||
change the way it works!
|
||||
|
||||
> Hint: We will get back to this, but to learn what resources an Evennia parent like `DefaultObject` offers,
|
||||
> easiest is to peek at its [API documentation](api:evennia.objects.objects#DefaultObject). The docstring for
|
||||
> easiest is to peek at its [API documentation](evennia.objects.objects.DefaultObject). The docstring for
|
||||
> the `Object` class can also help.
|
||||
|
||||
One thing that Evennia classes offers and which you don't get with vanilla Python classes is _persistence_. As
|
||||
|
|
@ -110,7 +110,7 @@ from `DefaultObject`, just from further away!
|
|||
|
||||
First reload the server as usual. We will need to create the dragon a little differently this time:
|
||||
|
||||
```sidebar:: Keyword arguments
|
||||
```{sidebar} Keyword arguments
|
||||
|
||||
Keyword arguments (like `db_key="Smaug"`) is a way to
|
||||
name the input arguments to a function or method. They make
|
||||
|
|
@ -251,7 +251,7 @@ You are specifying exactly which typeclass you want to use to build the Giantess
|
|||
desc = You see nothing special.
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
We used the `examine` command briefly in the [lesson about building in-game](./Building-Quickstart). Now these lines
|
||||
We used the `examine` command briefly in the [lesson about building in-game](./Building-Quickstart.md). Now these lines
|
||||
may be more useful to us:
|
||||
- **Name/key** - The name of this thing. The value `(#14)` is probably different for you. This is the
|
||||
unique 'primary key' or _dbref_ for this entity in the database.
|
||||
|
|
@ -294,7 +294,7 @@ But the reason Evennia knows to fall back to this class is not hard-coded - it's
|
|||
in [evennia/settings_default.py](https://github.com/evennia/evennia/blob/master/evennia/settings_default.py#L465),
|
||||
with the name `BASE_OBJECT_TYPECLASS`, which is set to `typeclasses.objects.Object`.
|
||||
|
||||
```sidebar:: Changing things
|
||||
```{sidebar} Changing things
|
||||
|
||||
While it's tempting to change folders around to your liking, this can
|
||||
make it harder to follow tutorials and may confuse if
|
||||
|
|
@ -357,7 +357,7 @@ You got a lot longer output this time. You have a lot more going on than a simpl
|
|||
- **Session id(s)**: This identifies the _Session_ (that is, the individual connection to a player's game client).
|
||||
- **Account** shows, well the `Account` object associated with this Character and Session.
|
||||
- **Stored/Merged Cmdsets** and **Commands available** is related to which _Commands_ are stored on you. We will
|
||||
get to them in the [next lesson](./Adding-Commands). For now it's enough to know these consitute all the
|
||||
get to them in the [next lesson](./Adding-Commands.md). For now it's enough to know these consitute all the
|
||||
commands available to you at a given moment.
|
||||
- **Non-Persistent attributes** are Attributes that are only stored temporarily and will go away on next reload.
|
||||
|
||||
|
|
@ -391,7 +391,7 @@ class Character(DefaultCharacter):
|
|||
> py self.get_stats()
|
||||
(10, 12, 15)
|
||||
|
||||
```sidebar:: Tuples and lists
|
||||
```{sidebar} Tuples and lists
|
||||
|
||||
- A `list` is written `[a, b, c, d, ...]`. It can be modified after creation.
|
||||
- A `tuple` is written `(a, b, c, ...)`. It cannot be modified once created.
|
||||
|
|
@ -451,7 +451,7 @@ class Character(DefaultCharacter):
|
|||
return self.db.str, self.db.dex, self.db.int
|
||||
```
|
||||
|
||||
```sidebar:: Spaces in Attribute name?
|
||||
```{sidebar} Spaces in Attribute name?
|
||||
|
||||
What if you want spaces in your Attribute name? Or you want to assign the
|
||||
name of the Attribute on-the fly? Then you can use `.attributes.add(name, value)` instead,
|
||||
|
|
@ -506,7 +506,7 @@ in more detail. For now, let's give every new character some random stats to sta
|
|||
We want those stats to be set only once, when the object is first created. For the Character, this method
|
||||
is called `at_object_creation`.
|
||||
|
||||
```sidebar:: __init__ vs at_object_creation
|
||||
```{sidebar} __init__ vs at_object_creation
|
||||
|
||||
For the `Monster` class we used `__init__` to set up the class. We can't use this
|
||||
for a typeclass because it will be called more than once, at the very least after
|
||||
|
|
@ -583,7 +583,7 @@ this is done (still in python multi-line mode):
|
|||
> for char in Character.objects.all()
|
||||
> char.at_object_creation()
|
||||
|
||||
```sidebar:: Database queries
|
||||
```{sidebar} Database queries
|
||||
|
||||
`Character.objects.all()` is an example of a database query expressed in Python. This will be converted
|
||||
into a database query under the hood. This syntax is part of
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ The `parse` method is called before `func` and has access to all the same on-com
|
|||
your parsing - if you wanted some other Command to also understand input on the form `<arg> with <arg>` you'd inherit
|
||||
from this class and just implement the `func` needed for that command without implementing `parse` anew.
|
||||
|
||||
```sidebar:: Tuples and Lists
|
||||
```{sidebar} Tuples and Lists
|
||||
|
||||
- A `list` is written as `[a, b, c, d, ...]`. You can add and grow/shrink a list after it was first created.
|
||||
- A `tuple` is written as `(a, b, c, d, ...)`. A tuple cannot be modified once it is created.
|
||||
|
|
@ -143,8 +143,8 @@ change (no code changed, only stuff in the database).
|
|||
## Adding a Command to an object
|
||||
|
||||
The commands of a cmdset attached to an object with `obj.cmdset.add()` will by default be made available to that object
|
||||
but _also to those in the same location as that object_. If you did the [Building introduction](./Building-Quickstart)
|
||||
you've seen an example of this with the "Red Button" object. The [Tutorial world](./Tutorial-World-Introduction)
|
||||
but _also to those in the same location as that object_. If you did the [Building introduction](./Building-Quickstart.md)
|
||||
you've seen an example of this with the "Red Button" object. The [Tutorial world](./Tutorial-World-Introduction.md)
|
||||
also has many examples of objects with commands on them.
|
||||
|
||||
To show how this could work, let's put our 'hit' Command on our simple `sword` object from the previous section.
|
||||
|
|
@ -161,7 +161,7 @@ Let's try to swing it!
|
|||
hit-1 (sword #11)
|
||||
hit-2
|
||||
|
||||
```sidebar:: Multi-matches
|
||||
```{sidebar} Multi-matches
|
||||
|
||||
Some game engines will just pick the first hit when finding more than one.
|
||||
Evennia will always give you a choice. The reason for this is that Evennia
|
||||
|
|
@ -206,7 +206,7 @@ Let's get a little ahead of ourselves and make it so you have to _hold_ the swor
|
|||
be available. This involves a _Lock_. We've cover locks in more detail later, just know that they are useful
|
||||
for limiting the kind of things you can do with an object, including limiting just when you can call commands on
|
||||
it.
|
||||
```sidebar:: Locks
|
||||
```{sidebar} Locks
|
||||
|
||||
Evennia Locks are defined as a mini-language defined in `lockstrings`. The lockstring
|
||||
is on a form `<situation>:<lockfuncs>`, where `situation` determines when this
|
||||
|
|
@ -221,7 +221,7 @@ this object if you are _holding_ the object (that is, it's in your inventory).
|
|||
|
||||
For locks to work, you cannot be _superuser_, since the superuser passes all locks. You need to `quell` yourself
|
||||
first:
|
||||
```sidebar:: quell/unquell
|
||||
```{sidebar} quell/unquell
|
||||
|
||||
Quelling allows you as a developer to take on the role of players with less
|
||||
priveleges. This is useful for testing and debugging, in particular since a
|
||||
|
|
@ -320,7 +320,7 @@ class SessionCmdSet(default_cmds.SessionCmdSet):
|
|||
#
|
||||
```
|
||||
|
||||
```sidebar:: super()
|
||||
```{sidebar} super()
|
||||
|
||||
The `super()` function refers to the parent of the current class and is commonly
|
||||
used to call same-named methods on the parent.
|
||||
|
|
@ -474,7 +474,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|||
self.add(mycommands.MyCmdGet)
|
||||
# ...
|
||||
```
|
||||
```sidebar:: Another way
|
||||
```{sidebar} Another way
|
||||
|
||||
Instead of adding `MyCmdGet` explicitly in default_cmdset.py,
|
||||
you could also add it to `mycommands.MyCmdSet` and let it be
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ which is a mature and professional programming language that is very fast to wor
|
|||
|
||||
That said, even though Python is widely considered easy to learn, we can only cover the most immediately
|
||||
important aspects of Python in this series of starting tutorials. Hopefully we can get you started
|
||||
but then you'll need to continue learning from there. See our [link section](../../../Links) for finding
|
||||
but then you'll need to continue learning from there. See our [link section](../../../Links.md) for finding
|
||||
more reference material and dedicated Python tutorials.
|
||||
|
||||
> While this will be quite basic if you are an experienced developer, you may want to at least
|
||||
|
|
@ -17,7 +17,7 @@ superuser powers back:
|
|||
unquell
|
||||
|
||||
|
||||
### Evennia Hello world
|
||||
## Evennia Hello world
|
||||
|
||||
The `py` Command (or `!`, which is an alias) allows you as a superuser to execute raw Python from in-
|
||||
game. This is useful for quick testing. From the game's input line, enter the following:
|
||||
|
|
@ -25,7 +25,7 @@ game. This is useful for quick testing. From the game's input line, enter the fo
|
|||
> py print("Hello World!")
|
||||
|
||||
|
||||
```sidebar:: Command input
|
||||
```{sidebar} Command input
|
||||
|
||||
The line with `>` indicates input to enter in-game, while the lines below are the
|
||||
expected return from that input.
|
||||
|
|
@ -43,7 +43,7 @@ Python accepts both. A third variant is triple-quotes (`"""..."""` or `'''...'''
|
|||
lines and are common for larger text-blocks. The way we use the `py` command right now only supports
|
||||
single-line input however.
|
||||
|
||||
### Making some text 'graphics'
|
||||
## Making some text 'graphics'
|
||||
|
||||
When making a text-game you will, unsurprisingly, be working a lot with text. Even if you have the occational
|
||||
button or even graphical element, the normal process is for the user to input commands as
|
||||
|
|
@ -72,7 +72,8 @@ is to use the `.format` _method_ of the string:
|
|||
> py print("This is a {} idea!".format("good"))
|
||||
This is a good idea!
|
||||
|
||||
```sidebar:: Functions and Methods
|
||||
```{eval-rst}
|
||||
.. sidebar:: Functions and Methods
|
||||
|
||||
Function:
|
||||
Something that performs and action when you `call` it with zero or more `arguments`. A function
|
||||
|
|
@ -111,7 +112,7 @@ To separate two Python instructions on the same line, you use the semi-colon, `;
|
|||
> py a = "awesome sauce" ; print("This is {}!".format(a))
|
||||
This is awesome sauce!
|
||||
|
||||
```warning:: MUD clients and semi-colon
|
||||
```{warning} MUD clients and semi-colon
|
||||
|
||||
Some MUD clients use the semi-colon `;` to split client-inputs
|
||||
into separate sends. If so, the above will give an error. Most clients allow you to
|
||||
|
|
@ -169,7 +170,7 @@ gives the normal text color. You can also use RGB (Red-Green-Blue) values from 0
|
|||
|
||||
Use the commands `color ansi` or `color xterm` to see which colors are available. Experiment!
|
||||
|
||||
### Importing code from other modules
|
||||
## Importing code from other modules
|
||||
|
||||
As we saw in the previous sections, we used `.format` to format strings and `me.msg` to access
|
||||
the `msg` method on `me`. This use of the full-stop character is used to access all sorts of resources,
|
||||
|
|
@ -191,7 +192,7 @@ For now, only add one line to `test.py`:
|
|||
print("Hello World!")
|
||||
```
|
||||
|
||||
```sidebar:: Python module
|
||||
```{sidebar} Python module
|
||||
|
||||
This is a text file with the `.py` file ending. A module
|
||||
contains Python source code and from within Python one can
|
||||
|
|
@ -306,7 +307,7 @@ print our text. We can now redo this as many times as we want without having to
|
|||
> py import world.test ; world.test.hello_world()
|
||||
Hello world!
|
||||
|
||||
### Sending text to others
|
||||
## Sending text to others
|
||||
|
||||
The `print` command is a standard Python structure. We can use that here in the `py` command since
|
||||
we can se the output. It's great for debugging and quick testing. But if you need to send a text
|
||||
|
|
@ -332,7 +333,7 @@ For now, `print` and `me.msg` behaves the same, just remember that `print` is ma
|
|||
debugging and `.msg()` will be more useful for you in the future.
|
||||
|
||||
|
||||
### Parsing Python errors
|
||||
## Parsing Python errors
|
||||
|
||||
Let's try this new text-sending in the function we just created. Go back to
|
||||
your `test.py` file and Replace the function with this instead:
|
||||
|
|
@ -355,7 +356,7 @@ File "./world/test.py", line 2, in hello_world
|
|||
NameError: name 'me' is not defined
|
||||
```
|
||||
|
||||
```sidebar:: Errors in the logs
|
||||
```{sidebar} Errors in the logs
|
||||
|
||||
In regular use, tracebacks will often appear in the log rather than
|
||||
in the game. Use `evennia --log` to view the log in the terminal. Make
|
||||
|
|
@ -386,7 +387,7 @@ reserved word (as mentioned, it's just something Evennia came up with for conven
|
|||
command). As far as the module is concerned `me` is an unfamiliar name, appearing out of nowhere.
|
||||
Hence the `NameError`.
|
||||
|
||||
### Passing arguments to functions
|
||||
## Passing arguments to functions
|
||||
|
||||
We know that `me` exists at the point when we run the `py` command, because we can do `py me.msg("Hello World!")`
|
||||
with no problem. So let's _pass_ that me along to the function so it knows what it should be.
|
||||
|
|
@ -419,7 +420,7 @@ suitable targets.
|
|||
>and the concept of _Leap before you Look_.
|
||||
|
||||
|
||||
### Finding others to send to
|
||||
## Finding others to send to
|
||||
|
||||
Let's wrap up this first Python `py` crash-course by finding someone else to send to.
|
||||
|
||||
|
|
@ -431,7 +432,7 @@ On the game command-line, let's create a mirror:
|
|||
|
||||
> create/drop mirror:contrib.tutorial_examples.mirror.TutorialMirror
|
||||
|
||||
```sidebar:: Creating objects
|
||||
```{sidebar} Creating objects
|
||||
|
||||
The `create` command was first used to create boxes in the
|
||||
`Building Stuff <Building-Quickstart>`_ tutorial. Note how it
|
||||
|
|
@ -466,7 +467,7 @@ Make sure you are in the same location as the mirror and try:
|
|||
`me.search("name")` will, by default, search and _return_ an object with the given name found in _the same location_
|
||||
as the `me` object is. If it can't find anything you'll see an error.
|
||||
|
||||
```sidebar:: Function returns
|
||||
```{sidebar} Function returns
|
||||
|
||||
Whereas a function like `print` only prints its arguments, it's very common
|
||||
for functions/methods to `return` a result of some kind. Think of the function
|
||||
|
|
@ -490,7 +491,7 @@ The mirror is useful for testing because its `.msg` method just echoes whatever
|
|||
would be to talk to a player character, in which case the text you sent would have appeared in their game client.
|
||||
|
||||
|
||||
### Multi-line py
|
||||
## Multi-line py
|
||||
|
||||
So far we have use `py` in single-line mode, using `;` to separate multiple inputs. This is very convenient
|
||||
when you want to do some quick testing. But you can also start a full multi-line Python interactive interpreter
|
||||
|
|
@ -524,7 +525,7 @@ the `>>>`). For brevity in this tutorual we'll turn the echo off. First exit `py
|
|||
[GCC 8.2.0] on Linux
|
||||
[py mode - quit() to exit]
|
||||
|
||||
```sidebar:: interactive py
|
||||
```{sidebar} interactive py
|
||||
|
||||
- Start with `py`.
|
||||
- Use `py/noecho` if you don't want your input to be echoed for every line.
|
||||
|
|
@ -602,7 +603,7 @@ get at the first of them (counting starts from 0).
|
|||
|
||||
Use `Ctrl-D` (`Cmd-D` on Mac) or `quit()` to exit the Python console.
|
||||
|
||||
### ipython
|
||||
## ipython
|
||||
|
||||
The default Python shell is quite limited and ugly. It's *highly* recommended to install `ipython` instead. This
|
||||
is a much nicer, third-party Python interpreter with colors and many usability improvements.
|
||||
|
|
@ -614,8 +615,7 @@ If `ipython` is installed, `evennia shell` will use it automatically.
|
|||
evennia shell
|
||||
...
|
||||
IPython 7.4.0 -- An enhanced Interactive Python. Type '?' for help
|
||||
In [1]:
|
||||
You now have Tab-completion:
|
||||
In [1]: You now have Tab-completion:
|
||||
|
||||
> import evennia
|
||||
> evennia.<TAB>
|
||||
|
|
@ -631,7 +631,7 @@ want to see the entire source code.
|
|||
|
||||
As for the normal python interpreter, use `Ctrl-D`/`Cmd-D` or `quit()` to exit ipython.
|
||||
|
||||
```important:: Persistent code
|
||||
```{important} Persistent code
|
||||
|
||||
Common for both `py` and `python`/`ipython` is that the code you write is not persistent - it will
|
||||
be gone after you shut down the interpreter (but ipython will remember your input history). For making long-lasting
|
||||
|
|
@ -639,7 +639,7 @@ As for the normal python interpreter, use `Ctrl-D`/`Cmd-D` or `quit()` to exit i
|
|||
```
|
||||
|
||||
|
||||
## Conclusions
|
||||
# Conclusions
|
||||
|
||||
This covers quite a lot of basic Python usage. We printed and formatted strings, defined our own
|
||||
first function, fixed an error and even searched and talked to a mirror! Being able to access
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ We have also taken a look at what our game dir looks and what is where. Now we'l
|
|||
|
||||
## Importing things
|
||||
|
||||
No one writes something as big as an online game in one single huge file. Instead one breaks up the
|
||||
code into separate files (modules). Each module is dedicated to different purposes. Not only does
|
||||
it make things cleaner, organized and easier to understand. It also makes it easier to re-use code -
|
||||
you just import the resources you need and know you only get just what you requested. This makes
|
||||
No one writes something as big as an online game in one single huge file. Instead one breaks up the
|
||||
code into separate files (modules). Each module is dedicated to different purposes. Not only does
|
||||
it make things cleaner, organized and easier to understand. It also makes it easier to re-use code -
|
||||
you just import the resources you need and know you only get just what you requested. This makes
|
||||
it much easier to find errors and to know what code is good and which has issues.
|
||||
|
||||
> Evennia itself uses your code in the same way - you just tell it where a particular type of code is,
|
||||
> Evennia itself uses your code in the same way - you just tell it where a particular type of code is,
|
||||
and it will import and use it (often instead of its defaults).
|
||||
|
||||
We have already successfully imported things, for example:
|
||||
|
|
@ -19,11 +19,11 @@ We have already successfully imported things, for example:
|
|||
> py import world.test ; world.test.hello_world(me)
|
||||
Hello World!
|
||||
|
||||
In this example, on your hard drive, the files looks like this:
|
||||
In this example, on your hard drive, the files looks like this:
|
||||
|
||||
```
|
||||
mygame/
|
||||
world/
|
||||
world/
|
||||
test.py <- inside this file is a function hello_world
|
||||
|
||||
```
|
||||
|
|
@ -35,71 +35,73 @@ def hello_world(who):
|
|||
who.msg("Hello World!")
|
||||
```
|
||||
|
||||
```sidebar:: Remember:
|
||||
```{eval-rst}
|
||||
|
||||
- Indentation matters in Python
|
||||
.. sidebar:: Remember:
|
||||
|
||||
- Indentation matters in Python
|
||||
- So does capitalization
|
||||
- Use 4 `spaces` to indent, not tabs
|
||||
- Empty lines are fine
|
||||
- Anything on a line after a `#` is a `comment`, ignored by Python
|
||||
```
|
||||
|
||||
The _python_path_ describes the relation between Python resources, both between and inside
|
||||
Python _modules_ (that is, files ending with .py). A python-path separates each part of the
|
||||
path `.` and always skips the `.py` file endings. Also, Evennia already knows to start looking
|
||||
The _python_path_ describes the relation between Python resources, both between and inside
|
||||
Python _modules_ (that is, files ending with .py). A python-path separates each part of the
|
||||
path `.` and always skips the `.py` file endings. Also, Evennia already knows to start looking
|
||||
for python resources inside `mygame/` so this should never be specified. Hence
|
||||
|
||||
import world.test
|
||||
import world.test
|
||||
|
||||
The `import` Python instruction loads `world.test` so you have it available. You can now go "into"
|
||||
this module to get to the function you want:
|
||||
|
||||
|
||||
world.test.hello_world(me)
|
||||
|
||||
Using `import` like this means that you have to specify the full `world.test` every time you want
|
||||
to get to your function. Here's a more powerful form of import:
|
||||
to get to your function. Here's a more powerful form of import:
|
||||
|
||||
from world.test import hello_world
|
||||
|
||||
The `from ... import ...` is very, very common as long as you want to get something with a longer
|
||||
The `from ... import ...` is very, very common as long as you want to get something with a longer
|
||||
python path. It imports `hello_world` directly, so you can use it right away!
|
||||
|
||||
> py from world.test import hello_world ; hello_world(me)
|
||||
Hello World!
|
||||
|
||||
|
||||
Let's say your `test.py` module had a bunch of interesting functions. You could then import them
|
||||
all one by one:
|
||||
all one by one:
|
||||
|
||||
from world.test import hello_world, my_func, awesome_func
|
||||
|
||||
If there were _a lot_ of functions, you could instead just import `test` and get the function
|
||||
|
||||
If there were _a lot_ of functions, you could instead just import `test` and get the function
|
||||
from there when you need (without having to give the full `world.test` every time):
|
||||
|
||||
> from world import test ; test.hello_world(me
|
||||
Hello World!
|
||||
|
||||
You can also _rename_ stuff you import. Say for example that the module you import to already
|
||||
Hello World!
|
||||
|
||||
You can also _rename_ stuff you import. Say for example that the module you import to already
|
||||
has a function `hello_world` but we also want to use the one from `world/test.py`:
|
||||
|
||||
from world.test import hello_world as test_hello_world
|
||||
|
||||
The form `from ... import ... as ...` renames the import.
|
||||
|
||||
The form `from ... import ... as ...` renames the import.
|
||||
|
||||
> from world.test import hello_world as hw ; hw(me)
|
||||
Hello World!
|
||||
|
||||
> Avoid renaming unless it's to avoid a name-collistion like above - you want to make things as
|
||||
> easy to read as possible, and renaming adds another layer of potential confusion.
|
||||
|
||||
In [the basic intro to Python](./Python-basic-introduction) we learned how to open the in-game
|
||||
multi-line interpreter.
|
||||
|
||||
> py
|
||||
> Avoid renaming unless it's to avoid a name-collistion like above - you want to make things as
|
||||
> easy to read as possible, and renaming adds another layer of potential confusion.
|
||||
|
||||
In [the basic intro to Python](./Python-basic-introduction.md) we learned how to open the in-game
|
||||
multi-line interpreter.
|
||||
|
||||
> py
|
||||
Evennia Interactive Python mode
|
||||
Python 3.7.1 (default, Oct 22 2018, 11:21:55)
|
||||
[GCC 8.2.0] on Linux
|
||||
[py mode - quit() to exit]
|
||||
|
||||
[py mode - quit() to exit]
|
||||
|
||||
You now only need to import once to use the imported function over and over.
|
||||
|
||||
> from world.test import hello_world
|
||||
|
|
@ -112,14 +114,14 @@ You now only need to import once to use the imported function over and over.
|
|||
> quit()
|
||||
Closing the Python console.
|
||||
|
||||
The same goes when writing code in a module - in most Python modules you will see a bunch of
|
||||
The same goes when writing code in a module - in most Python modules you will see a bunch of
|
||||
imports at the top, resources that are then used by all code in that module.
|
||||
|
||||
## On classes and objects
|
||||
## On classes and objects
|
||||
|
||||
Now that we know about imports, let look at a real Evennia module and try to understand it.
|
||||
|
||||
Open `mygame/typeclasses/objects.py` in your text editor of choice.
|
||||
Open `mygame/typeclasses/objects.py` in your text editor of choice.
|
||||
|
||||
```python
|
||||
"""
|
||||
|
|
@ -134,30 +136,30 @@ class Object(DefaultObject):
|
|||
pass
|
||||
```
|
||||
|
||||
```sidebar:: Docstrings vs Comments
|
||||
```{sidebar} Docstrings vs Comments
|
||||
|
||||
A docstring is not the same as a comment (created by `#`). A
|
||||
docstring is not ignored by Python but is an integral part of the thing
|
||||
A docstring is not the same as a comment (created by `#`). A
|
||||
docstring is not ignored by Python but is an integral part of the thing
|
||||
it is documenting (the module and the class in this case).
|
||||
```
|
||||
The real file is much longer but we can ignore the multi-line strings (`""" ... """`). These serve
|
||||
as documentation-strings, or _docstrings_ for the module (at the top) and the `class` below.
|
||||
The real file is much longer but we can ignore the multi-line strings (`""" ... """`). These serve
|
||||
as documentation-strings, or _docstrings_ for the module (at the top) and the `class` below.
|
||||
|
||||
Below the module doc string we have the import. In this case we are importing a resource
|
||||
from the core `evennia` library itself. We will dive into this later, for now we just treat this
|
||||
as a black box.
|
||||
as a black box.
|
||||
|
||||
Next we have a `class` named `Object`, which _inherits_ from `DefaultObject`. This class doesn't
|
||||
actually do anything on its own, its only code (except the docstring) is `pass` which means,
|
||||
well, to pass and don't do anything.
|
||||
well, to pass and don't do anything.
|
||||
|
||||
We will get back to this module in the [next lesson](./Learning-Typeclasses). First we need to do a
|
||||
little detour to understand what a 'class', an 'object' or 'instance' is. These are fundamental
|
||||
things to understand before you can use Evennia efficiently.
|
||||
```sidebar:: OOP
|
||||
We will get back to this module in the [next lesson](./Learning-Typeclasses.md). First we need to do a
|
||||
little detour to understand what a 'class', an 'object' or 'instance' is. These are fundamental
|
||||
things to understand before you can use Evennia efficiently.
|
||||
```{sidebar} OOP
|
||||
|
||||
Classes, objects, instances and inheritance are fundamental to Python. This and some
|
||||
other concepts are often clumped together under the term Object-Oriented-Programming (OOP).
|
||||
Classes, objects, instances and inheritance are fundamental to Python. This and some
|
||||
other concepts are often clumped together under the term Object-Oriented-Programming (OOP).
|
||||
```
|
||||
|
||||
### Classes and instances
|
||||
|
|
@ -179,57 +181,58 @@ class Monster:
|
|||
|
||||
```
|
||||
|
||||
Above we have defined a `Monster` class with one variable `key` (that is, the name) and one
|
||||
_method_ on it. A method is like a function except it sits "on" the class. It also always has
|
||||
at least one argument (almost always written as `self` although you could in principle use
|
||||
Above we have defined a `Monster` class with one variable `key` (that is, the name) and one
|
||||
_method_ on it. A method is like a function except it sits "on" the class. It also always has
|
||||
at least one argument (almost always written as `self` although you could in principle use
|
||||
another name), which is a reference back to itself. So when we print `self.key` we are referring
|
||||
back to the `key` on the class.
|
||||
|
||||
```sidebar:: Terms
|
||||
```{eval-rst}
|
||||
.. sidebar:: Terms
|
||||
|
||||
- A `class` is a code template describing a 'type' of something
|
||||
- An `object` is an `instance` of a `class`. Like using a mold to cast tin soldiers, one class can be `instantiated` into any number of object-instances.
|
||||
- An `object` is an `instance` of a `class`. Like using a mold to cast tin soldiers, one class can be `instantiated` into any number of object-instances.
|
||||
|
||||
```
|
||||
A class is just a template. Before it can be used, we must create an _instance_ of the class. If
|
||||
`Monster` is a class, then an instance is Fluffy, the individual red dragon. You instantiate
|
||||
by _calling_ the class, much like you would a function:
|
||||
`Monster` is a class, then an instance is Fluffy, the individual red dragon. You instantiate
|
||||
by _calling_ the class, much like you would a function:
|
||||
|
||||
fluffy = Monster()
|
||||
|
||||
|
||||
Let's try it in-game (we use multi-line mode, it's easier)
|
||||
|
||||
> py
|
||||
> py
|
||||
> from typeclasses.monsters import Monster
|
||||
> fluffy = Monster()
|
||||
> fluffy.move_around()
|
||||
Monster is moving!
|
||||
|
||||
We created an _instance_ of `Monster`, which we stored in the variable `fluffy`. We then
|
||||
called the `move_around` method on fluffy to get the printout.
|
||||
We created an _instance_ of `Monster`, which we stored in the variable `fluffy`. We then
|
||||
called the `move_around` method on fluffy to get the printout.
|
||||
|
||||
> Note how we _didn't_ call the method as `fluffy.move_around(self)`. While the `self` has to be
|
||||
> there when defining the method, we _never_ add it explicitly when we call the method (Python
|
||||
> will add the correct `self` for us automatically behind the scenes).
|
||||
> Note how we _didn't_ call the method as `fluffy.move_around(self)`. While the `self` has to be
|
||||
> there when defining the method, we _never_ add it explicitly when we call the method (Python
|
||||
> will add the correct `self` for us automatically behind the scenes).
|
||||
|
||||
Let's create the sibling of Fluffy, Cuddly:
|
||||
|
||||
> cuddly = Monster()
|
||||
> cuddly.move_around()
|
||||
Monster is moving!
|
||||
Monster is moving!
|
||||
|
||||
We now have two dragons and they'll hang around until with call `quit()` to exit this Python
|
||||
instance. We can have them move as many times as we want. But no matter how many dragons we
|
||||
We now have two dragons and they'll hang around until with call `quit()` to exit this Python
|
||||
instance. We can have them move as many times as we want. But no matter how many dragons we
|
||||
create, they will all show the same printout since `key` is always fixed as "Monster".
|
||||
|
||||
Let's make the class a little more flexible:
|
||||
Let's make the class a little more flexible:
|
||||
|
||||
```python
|
||||
|
||||
class Monster:
|
||||
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = key
|
||||
self.key = key
|
||||
|
||||
def move_around(self):
|
||||
print(f"{self.key} is moving!")
|
||||
|
|
@ -237,7 +240,7 @@ class Monster:
|
|||
```
|
||||
|
||||
The `__init__` is a special method that Python recognizes. If given, this handles extra arguments
|
||||
when you instantiate a new Monster. We have it add an argument `key` that we store on `self`.
|
||||
when you instantiate a new Monster. We have it add an argument `key` that we store on `self`.
|
||||
|
||||
Now, for Evennia to see this code change, we need to reload the server. You can either do it this
|
||||
way:
|
||||
|
|
@ -245,13 +248,13 @@ way:
|
|||
> quit()
|
||||
Python Console is closing.
|
||||
> reload
|
||||
|
||||
Or you can use a separate terminal and restart from outside the game:
|
||||
```sidebar:: On reloading
|
||||
|
||||
Or you can use a separate terminal and restart from outside the game:
|
||||
```{sidebar} On reloading
|
||||
|
||||
Reloading with the python mode gets a little annoying since you need to redo everything
|
||||
after every reload. Just keep in mind that during regular development you will not be
|
||||
working this way. The in-game python mode is practical for quick fixes and experiments like
|
||||
after every reload. Just keep in mind that during regular development you will not be
|
||||
working this way. The in-game python mode is practical for quick fixes and experiments like
|
||||
this, but actual code is normally written externally, in python modules.
|
||||
```
|
||||
|
||||
|
|
@ -259,50 +262,50 @@ Or you can use a separate terminal and restart from outside the game:
|
|||
|
||||
Either way you'll need to go into `py` again:
|
||||
|
||||
> py
|
||||
> py
|
||||
> from typeclasses.monsters import Monster
|
||||
fluffy = Monster("Fluffy")
|
||||
fluffy.move_around()
|
||||
Fluffy is moving!
|
||||
Fluffy is moving!
|
||||
|
||||
Now we passed `"Fluffy"` as an argument to the class. This went into `__init__` and set `self.key`, which we
|
||||
Now we passed `"Fluffy"` as an argument to the class. This went into `__init__` and set `self.key`, which we
|
||||
later used to print with the right name! Again, note that we didn't include `self` when calling.
|
||||
|
||||
### What's so good about objects?
|
||||
### What's so good about objects?
|
||||
|
||||
So far all we've seen a class do is to behave our first `hello_world` function but more complex. We
|
||||
So far all we've seen a class do is to behave our first `hello_world` function but more complex. We
|
||||
could just have made a function:
|
||||
|
||||
```python
|
||||
def monster_move_around(key):
|
||||
print(f"{key} is moving!")
|
||||
print(f"{key} is moving!")
|
||||
```
|
||||
|
||||
The difference between the function and an instance of a class (the object), is that the
|
||||
The difference between the function and an instance of a class (the object), is that the
|
||||
object retains _state_. Once you called the function it forgets everything about what you called
|
||||
it with last time. The object, on the other hand, remembers changes:
|
||||
it with last time. The object, on the other hand, remembers changes:
|
||||
|
||||
> fluffy.key = "Cuddly"
|
||||
> fluffy.move_around()
|
||||
Cuddly is moving!
|
||||
|
||||
The `fluffy` object's `key` was changed to "Cuddly" for as long as it's around. This makes objects
|
||||
extremely useful for representing and remembering collections of data - some of which can be other
|
||||
objects in turn:
|
||||
Cuddly is moving!
|
||||
|
||||
- A player character with all its stats
|
||||
The `fluffy` object's `key` was changed to "Cuddly" for as long as it's around. This makes objects
|
||||
extremely useful for representing and remembering collections of data - some of which can be other
|
||||
objects in turn:
|
||||
|
||||
- A player character with all its stats
|
||||
- A monster with HP
|
||||
- A chest with a number of gold coins in it
|
||||
- A room with other objects inside it
|
||||
- The current policy positions of a political party
|
||||
- A rule with methods for resolving challenges or roll dice
|
||||
- A multi-dimenstional data-point for a complex economic simulation
|
||||
- And so much more!
|
||||
- And so much more!
|
||||
|
||||
### Classes can have children
|
||||
|
||||
Classes can _inherit_ from each other. A "child" class will inherit everything from its "parent" class. But if
|
||||
the child adds something with the same name as its parent, it will _override_ whatever it got from its parent.
|
||||
Classes can _inherit_ from each other. A "child" class will inherit everything from its "parent" class. But if
|
||||
the child adds something with the same name as its parent, it will _override_ whatever it got from its parent.
|
||||
|
||||
Let's expand `mygame/typeclasses/monsters.py` with another class:
|
||||
|
||||
|
|
@ -312,9 +315,9 @@ class Monster:
|
|||
"""
|
||||
This is a base class for Monster.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = key
|
||||
self.key = key
|
||||
|
||||
def move_around(self):
|
||||
print(f"{self.key} is moving!")
|
||||
|
|
@ -329,7 +332,7 @@ class Dragon(Monster):
|
|||
print(f"{self.key} flies through the air high above!")
|
||||
|
||||
def firebreath(self):
|
||||
"""
|
||||
"""
|
||||
Let our dragon breathe fire.
|
||||
"""
|
||||
print(f"{self.key} breathes fire!")
|
||||
|
|
@ -339,10 +342,10 @@ class Dragon(Monster):
|
|||
We added some docstrings for clarity. It's always a good idea to add doc strings; you can do so also for methods,
|
||||
as exemplified for the new `firebreath` method.
|
||||
|
||||
We created the new class `Dragon` but we also specified that `Monster` is the _parent_ of `Dragon` but adding
|
||||
the parent in parenthesis. `class Classname(Parent)` is the way to do this.
|
||||
We created the new class `Dragon` but we also specified that `Monster` is the _parent_ of `Dragon` but adding
|
||||
the parent in parenthesis. `class Classname(Parent)` is the way to do this.
|
||||
|
||||
```sidebar:: Multi-inheritance
|
||||
```{sidebar} Multi-inheritance
|
||||
|
||||
It's possible to add more comma-separated parents to a class. You should usually avoid
|
||||
this until you `really` know what you are doing. A single parent will be enough for almost
|
||||
|
|
@ -352,61 +355,61 @@ the parent in parenthesis. `class Classname(Parent)` is the way to do this.
|
|||
|
||||
Let's try out our new class. First `reload` the server and the do
|
||||
|
||||
> py
|
||||
> from typeclasses.monsters import Dragon
|
||||
> py
|
||||
> from typeclasses.monsters import Dragon
|
||||
> smaug = Dragon("Smaug")
|
||||
> smaug.move_around()
|
||||
Smaug flies through the air high above!
|
||||
> smaug.firebreath()
|
||||
Smaug breathes fire!
|
||||
|
||||
Because we didn't implement `__init__` in `Dragon`, we got the one from `Monster` instead. But since we
|
||||
implemented our own `move_around` in `Dragon`, it _overrides_ the one in `Monster`. And `firebreath` is only
|
||||
Smaug breathes fire!
|
||||
|
||||
Because we didn't implement `__init__` in `Dragon`, we got the one from `Monster` instead. But since we
|
||||
implemented our own `move_around` in `Dragon`, it _overrides_ the one in `Monster`. And `firebreath` is only
|
||||
available for `Dragon`s of course. Having that on `Monster` would not have made much sense, since not every monster
|
||||
can breathe fire.
|
||||
can breathe fire.
|
||||
|
||||
One can also force a class to use resources from the parent even if you are overriding some of it. This is done
|
||||
with the `super()` method. Modify your `Dragon` class as follows:
|
||||
|
||||
|
||||
```python
|
||||
# ...
|
||||
# ...
|
||||
|
||||
class Dragon(Monster):
|
||||
|
||||
def move_around(self):
|
||||
super().move_around()
|
||||
print("The world trembles.")
|
||||
|
||||
|
||||
# ...
|
||||
```
|
||||
> Keep `Monster` and the `firebreath` method, `# ...` indicates the rest of the code is untouched.
|
||||
> Keep `Monster` and the `firebreath` method, `# ...` indicates the rest of the code is untouched.
|
||||
>
|
||||
|
||||
The `super().move_around()` line means that we are calling `move_around()` on the parent of the class. So in this
|
||||
case, we will call `Monster.move_around` first, before doing our own thing.
|
||||
The `super().move_around()` line means that we are calling `move_around()` on the parent of the class. So in this
|
||||
case, we will call `Monster.move_around` first, before doing our own thing.
|
||||
|
||||
Now `reload` the server and then:
|
||||
Now `reload` the server and then:
|
||||
|
||||
> py
|
||||
> from typeclasses.monsters import Dragon
|
||||
> py
|
||||
> from typeclasses.monsters import Dragon
|
||||
> smaug = Dragon("Smaug")
|
||||
> smaug.move_around()
|
||||
Smaug is moving!
|
||||
The world trembles.
|
||||
|
||||
We can see that `Monster.move_around()` is calls first and prints "Smaug is moving!", followed by the extra bit
|
||||
We can see that `Monster.move_around()` is calls first and prints "Smaug is moving!", followed by the extra bit
|
||||
about the trembling world we added in the `Dragon` class.
|
||||
|
||||
Inheritance is very powerful because it allows you to organize and re-use code while only adding the special things
|
||||
you want to change. Evennia uses this concept a lot.
|
||||
you want to change. Evennia uses this concept a lot.
|
||||
|
||||
## Summary
|
||||
|
||||
We have created our first dragons from classes. We have learned a little about how you _instantiate_ a class
|
||||
We have created our first dragons from classes. We have learned a little about how you _instantiate_ a class
|
||||
into an _object_. We have seen some examples of _inheritance_ and we tested to _override_ a method in the parent
|
||||
with one in the child class. We also used `super()` to good effect.
|
||||
|
||||
We have used pretty much raw Python so far. In the coming lessons we'll start to look at the extra bits that Evennia
|
||||
We have used pretty much raw Python so far. In the coming lessons we'll start to look at the extra bits that Evennia
|
||||
provides. But first we need to learn just where to find everything.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
# Searching for things
|
||||
|
||||
We have gone through how to create the various entities in Evennia. But creating something is of little use
|
||||
if we cannot find and use it afterwards.
|
||||
We have gone through how to create the various entities in Evennia. But creating something is of little use
|
||||
if we cannot find and use it afterwards.
|
||||
|
||||
## Main search functions
|
||||
|
||||
The base tools are the `evennia.search_*` functions, such as `evennia.search_object`.
|
||||
The base tools are the `evennia.search_*` functions, such as `evennia.search_object`.
|
||||
|
||||
rose = evennia.search_object(key="rose")
|
||||
acct = evennia.search_account(key="MyAccountName", email="foo@bar.com")
|
||||
|
||||
```sidebar:: Querysets
|
||||
```{sidebar} Querysets
|
||||
|
||||
What is returned from the main search functions is actually a `queryset`. They can be
|
||||
What is returned from the main search functions is actually a `queryset`. They can be
|
||||
treated like lists except that they can't modified in-place. We'll discuss querysets in
|
||||
the `next lesson` <Django-queries>`_.
|
||||
```
|
||||
|
||||
Strings are always case-insensitive, so searching for `"rose"`, `"Rose"` or `"rOsE"` give the same results.
|
||||
It's important to remember that what is returned from these search methods is a _listing_ of 0, one or more
|
||||
elements - all the matches to your search. To get the first match:
|
||||
It's important to remember that what is returned from these search methods is a _listing_ of 0, one or more
|
||||
elements - all the matches to your search. To get the first match:
|
||||
|
||||
rose = rose[0]
|
||||
rose = rose[0]
|
||||
|
||||
Often you really want all matches to the search parameters you specify. In other situations, having zero or
|
||||
more than one match is a sign of a problem and you need to handle this case yourself.
|
||||
Often you really want all matches to the search parameters you specify. In other situations, having zero or
|
||||
more than one match is a sign of a problem and you need to handle this case yourself.
|
||||
|
||||
the_one_ring = evennia.search_object(key="The one Ring")
|
||||
if not the_one_ring:
|
||||
|
|
@ -35,16 +35,16 @@ more than one match is a sign of a problem and you need to handle this case your
|
|||
# ok - exactly one ring found
|
||||
the_one_ring = the_one_ring[0]
|
||||
|
||||
There are equivalent search functions for all the main resources. You can find a listing of them
|
||||
[in the Search functions section](../../../Evennia-API) of the API frontpage.
|
||||
There are equivalent search functions for all the main resources. You can find a listing of them
|
||||
[in the Search functions section](../../../Evennia-API.md) of the API frontpage.
|
||||
|
||||
## Searching using Object.search
|
||||
|
||||
On the `DefaultObject` is a `.search` method which we have already tried out when we made Commands. For
|
||||
this to be used you must already have an object available:
|
||||
On the `DefaultObject` is a `.search` method which we have already tried out when we made Commands. For
|
||||
this to be used you must already have an object available:
|
||||
|
||||
rose = obj.search("rose")
|
||||
|
||||
|
||||
The `.search` method wraps `evennia.search_object` and handles its output in various ways.
|
||||
|
||||
- By default it will always search for objects among those in `obj.location.contents` and `obj.contents` (that is,
|
||||
|
|
@ -52,7 +52,7 @@ things in obj's inventory or in the same room).
|
|||
- It will always return exactly one match. If it found zero or more than one match, the return is `None`.
|
||||
- On a no-match or multimatch, `.search` will automatically send an error message to `obj`.
|
||||
|
||||
So this method handles error messaging for you. A very common way to use it is in commands:
|
||||
So this method handles error messaging for you. A very common way to use it is in commands:
|
||||
|
||||
```python
|
||||
from evennia import Command
|
||||
|
|
@ -68,21 +68,21 @@ class MyCommand(Command):
|
|||
return
|
||||
```
|
||||
|
||||
Remember, `self.caller` is the one calling the command. This is usually a Character, which
|
||||
inherits from `DefaultObject`! This (rather stupid) Command searches for an object named "foo" in
|
||||
the same location. If it can't find it, `foo` will be `None`. The error has already been reported
|
||||
to `self.caller` so we just abort with `return`.
|
||||
Remember, `self.caller` is the one calling the command. This is usually a Character, which
|
||||
inherits from `DefaultObject`! This (rather stupid) Command searches for an object named "foo" in
|
||||
the same location. If it can't find it, `foo` will be `None`. The error has already been reported
|
||||
to `self.caller` so we just abort with `return`.
|
||||
|
||||
You can use `.search` to find anything, not just stuff in the same room:
|
||||
You can use `.search` to find anything, not just stuff in the same room:
|
||||
|
||||
volcano = self.caller.search("Volcano", global=True)
|
||||
|
||||
If you only want to search for a specific list of things, you can do so too:
|
||||
If you only want to search for a specific list of things, you can do so too:
|
||||
|
||||
stone = self.caller.search("MyStone", candidates=[obj1, obj2, obj3, obj4])
|
||||
|
||||
|
||||
This will only return a match if MyStone is one of the four provided candidate objects. This is quite powerful,
|
||||
here's how you'd find something only in your inventory:
|
||||
here's how you'd find something only in your inventory:
|
||||
|
||||
potion = self.caller.search("Healing potion", candidates=self.caller.contents)
|
||||
|
||||
|
|
@ -90,25 +90,25 @@ You can also turn off the automatic error handling:
|
|||
|
||||
swords = self.caller.search("Sword", quiet=True)
|
||||
|
||||
With `quiet=True` the user will not be notified on zero or multi-match errors. Instead you are expected to handle this
|
||||
yourself and what you get back is now a list of zero, one or more matches!
|
||||
With `quiet=True` the user will not be notified on zero or multi-match errors. Instead you are expected to handle this
|
||||
yourself and what you get back is now a list of zero, one or more matches!
|
||||
|
||||
## What can be searched for
|
||||
|
||||
These are the main database entities one can search for:
|
||||
|
||||
- [Objects](../../../Components/Objects)
|
||||
- [Accounts](../../../Components/Accounts)
|
||||
- [Scripts](../../../Components/Scripts),
|
||||
- [Channels](../../../Components/Communications#channels),
|
||||
- [Messages](Communication#Msg)
|
||||
- [Help Entries](../../../Components/Help-System).
|
||||
- [Objects](../../../Components/Objects.md)
|
||||
- [Accounts](../../../Components/Accounts.md)
|
||||
- [Scripts](../../../Components/Scripts.md),
|
||||
- [Channels](../../../Components/Channels.md),
|
||||
- [Messages](../../../Components/Msg.md)
|
||||
- [Help Entries](../../../Components/Help-System.md).
|
||||
|
||||
Most of the time you'll likely spend your time searching for Objects and the occasional Accounts.
|
||||
|
||||
So to find an entity, what can be searched for?
|
||||
So to find an entity, what can be searched for?
|
||||
|
||||
### Search by key
|
||||
### Search by key
|
||||
|
||||
The `key` is the name of the entity. Searching for this is always case-insensitive.
|
||||
|
||||
|
|
@ -118,29 +118,29 @@ Objects and Accounts can have any number of aliases. When searching for `key` th
|
|||
you can't easily search only for aliases.
|
||||
|
||||
rose.aliases.add("flower")
|
||||
|
||||
If the above `rose` has a `key` `"Rose"`, it can now also be found by searching for `flower`. In-game
|
||||
|
||||
If the above `rose` has a `key` `"Rose"`, it can now also be found by searching for `flower`. In-game
|
||||
you can assign new aliases to things with the `alias` command.
|
||||
|
||||
### Search by location
|
||||
|
||||
Only Objects (things inheriting from `evennia.DefaultObject`) has a location. This is usually a room.
|
||||
|
||||
Only Objects (things inheriting from `evennia.DefaultObject`) has a location. This is usually a room.
|
||||
The `Object.search` method will automatically limit it search by location, but it also works for the
|
||||
general search function. If we assume `room` is a particular Room instance,
|
||||
general search function. If we assume `room` is a particular Room instance,
|
||||
|
||||
chest = evennia.search_object("Treasure chest", location=room)
|
||||
chest = evennia.search_object("Treasure chest", location=room)
|
||||
|
||||
### Search by Tags
|
||||
### Search by Tags
|
||||
|
||||
Think of a [Tag](../../../Components/Tags) as the label the airport puts on your luggage when flying.
|
||||
Everyone going on the same plane gets a tag grouping them together so the airport can know what should
|
||||
Think of a [Tag](../../../Components/Tags.md) as the label the airport puts on your luggage when flying.
|
||||
Everyone going on the same plane gets a tag grouping them together so the airport can know what should
|
||||
go to which plane. Entities in Evennia can be grouped in the same way. Any number of tags can be attached
|
||||
to each object.
|
||||
to each object.
|
||||
|
||||
rose.tags.add("flowers")
|
||||
daffodil.tags.add("flowers")
|
||||
tulip.tags.add("flowers")
|
||||
|
||||
|
||||
You can now find all flowers using the `search_tag` function:
|
||||
|
||||
all_flowers = evennia.search_tag("flowers")
|
||||
|
|
@ -150,100 +150,100 @@ Tags can also have categories. By default this category is `None` which is also
|
|||
silmarillion.tags.add("fantasy", category="books")
|
||||
ice_and_fire.tags.add("fantasy", category="books")
|
||||
mona_lisa_overdrive.tags.add("cyberpunk", category="books")
|
||||
|
||||
Note that if you specify the tag you _must_ also include its category, otherwise that category
|
||||
|
||||
Note that if you specify the tag you _must_ also include its category, otherwise that category
|
||||
will be `None` and find no matches.
|
||||
|
||||
all_fantasy_books = evennia.search_tag("fantasy") # no matches!
|
||||
all_fantasy_books = evennia.search_tag("fantasy", category="books")
|
||||
|
||||
all_fantasy_books = evennia.search_tag("fantasy") # no matches!
|
||||
all_fantasy_books = evennia.search_tag("fantasy", category="books")
|
||||
|
||||
Only the second line above returns the two fantasy books. If we specify a category however,
|
||||
we can get all tagged entities within that category:
|
||||
we can get all tagged entities within that category:
|
||||
|
||||
all_books = evennia.search_tag(category="books")
|
||||
|
||||
This gets all three books.
|
||||
|
||||
|
||||
This gets all three books.
|
||||
|
||||
### Search by Attribute
|
||||
|
||||
We can also search by the [Attributes](../../../Components/Attributes) associated with entities.
|
||||
We can also search by the [Attributes](../../../Components/Attributes.md) associated with entities.
|
||||
|
||||
For example, let's give our rose thorns:
|
||||
For example, let's give our rose thorns:
|
||||
|
||||
rose.db.has_thorns = True
|
||||
wines.db.has_thorns = True
|
||||
daffodil.db.has_thorns = False
|
||||
|
||||
Now we can find things attribute and the value we want it to have:
|
||||
|
||||
daffodil.db.has_thorns = False
|
||||
|
||||
Now we can find things attribute and the value we want it to have:
|
||||
|
||||
is_ouch = evennia.search_object_attribute("has_thorns", True)
|
||||
|
||||
This returns the rose and the wines.
|
||||
This returns the rose and the wines.
|
||||
|
||||
> Searching by Attribute can be very practical. But if you plan to do a search very often, searching
|
||||
> by-tag is generally faster.
|
||||
> by-tag is generally faster.
|
||||
|
||||
|
||||
### Search by Typeclass
|
||||
|
||||
Sometimes it's useful to find all objects of a specific Typeclass. All of Evennia's search tools support this.
|
||||
|
||||
all_roses = evennia.search_object(typeclass="typeclasses.flowers.Rose")
|
||||
all_roses = evennia.search_object(typeclass="typeclasses.flowers.Rose")
|
||||
|
||||
If you have the `Rose` class already imported you can also pass it directly:
|
||||
|
||||
all_roses = evennia.search_object(typeclass=Rose)
|
||||
|
||||
|
||||
You can also search using the typeclass itself:
|
||||
|
||||
all_roses = Rose.objects.all()
|
||||
|
||||
This last way of searching is a simple form of a Django _query_. This is a way to express SQL queries using
|
||||
Python. We'll cover this some more as an [Extra-credits](#Extra-Credits) section at the end of this lesson.
|
||||
|
||||
### Search by dbref
|
||||
|
||||
The database id or `#dbref` is unique and never-reused within each database table. In search methods you can
|
||||
replace the search for `key` with the dbref to search for. This must be written as a string `#dbref`:
|
||||
This last way of searching is a simple form of a Django _query_. This is a way to express SQL queries using
|
||||
Python.
|
||||
|
||||
### Search by dbref
|
||||
|
||||
The database id or `#dbref` is unique and never-reused within each database table. In search methods you can
|
||||
replace the search for `key` with the dbref to search for. This must be written as a string `#dbref`:
|
||||
|
||||
the_answer = self.caller.search("#42")
|
||||
eightball = evennia.search_object("#8")
|
||||
eightball = evennia.search_object("#8")
|
||||
|
||||
Since `#dbref` is always unique, this search is always global.
|
||||
Since `#dbref` is always unique, this search is always global.
|
||||
|
||||
```warning:: Relying on #dbrefs
|
||||
```{warning} Relying on #dbrefs
|
||||
|
||||
You may be used to using #dbrefs a lot from other codebases. It is however considered
|
||||
You may be used to using #dbrefs a lot from other codebases. It is however considered
|
||||
`bad practice` in Evennia to rely on hard-coded #dbrefs. It makes your code hard to maintain
|
||||
and tied to the exact layout of the database. In 99% of cases you should pass the actual objects
|
||||
around and search by key/tags/attribute instead.
|
||||
and tied to the exact layout of the database. In 99% of cases you should pass the actual objects
|
||||
around and search by key/tags/attribute instead.
|
||||
```
|
||||
|
||||
## Finding objects relative each other
|
||||
|
||||
Let's consider a `chest` with a `coin` inside it. The chests stand in a room `dungeon`. In the dungeon is also
|
||||
a `door`. This is an exit leading outside.
|
||||
a `door`. This is an exit leading outside.
|
||||
|
||||
- `coin.location` is `chest`.
|
||||
- `chest.location` is `dungeon`.
|
||||
- `door.location` is `dungeon`.
|
||||
- `room.location` is `None` since it's not inside something else.
|
||||
- `room.location` is `None` since it's not inside something else.
|
||||
|
||||
One can use this to find what is inside what. For example, `coin.location.location` is the `room`.
|
||||
One can use this to find what is inside what. For example, `coin.location.location` is the `room`.
|
||||
We can also find what is inside each object. This is a list of things.
|
||||
|
||||
- `room.contents` is `[chest, door]`
|
||||
- `chest.contents` is `[coin]`
|
||||
- `coin.contents` is `[]`, the empty list since there's nothing 'inside' the coin.
|
||||
- `door.contents` is `[]` too.
|
||||
- `door.contents` is `[]` too.
|
||||
|
||||
A convenient helper is `.contents_get` - this allows to restrict what is returned:
|
||||
A convenient helper is `.contents_get` - this allows to restrict what is returned:
|
||||
|
||||
- `room.contents_get(exclude=chest)` - this returns everything in the room except the chest (maybe it's hidden?)
|
||||
|
||||
There is a special property for finding exits:
|
||||
There is a special property for finding exits:
|
||||
|
||||
- `room.exits` is `[door]`
|
||||
- `room.exits` is `[door]`
|
||||
- `coin.exits` is `[]` (same for all the other objects)
|
||||
|
||||
There is a property `.destination` which is only used by exits:
|
||||
|
|
@ -251,11 +251,11 @@ There is a property `.destination` which is only used by exits:
|
|||
- `door.destination` is `outside` (or wherever the door leads)
|
||||
- `room.destination` is `None` (same for all the other non-exit objects)
|
||||
|
||||
## Summary
|
||||
## Summary
|
||||
|
||||
Knowing how to find things is important and the tools from this section will serve you well. For most of your needs
|
||||
these tools will be all you need ...
|
||||
|
||||
... but not always. In the next lesson we will dive further into more complex searching when we look at
|
||||
... but not always. In the next lesson we will dive further into more complex searching when we look at
|
||||
Django queries and querysets in earnest.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,49 +1,51 @@
|
|||
# Starting Tutorial (Part 1)
|
||||
|
||||
```sidebar:: Tutorial Parts
|
||||
```{eval-rst}
|
||||
.. sidebar:: Tutorial Parts
|
||||
|
||||
**Part 1: What we have**
|
||||
A tour of Evennia and how to use the tools, including an introduction to Python.
|
||||
Part 2: `What we want <../Part2/Starting-Part2.html>`_
|
||||
Planning our tutorial game and what to think about when planning your own in the future.
|
||||
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
|
||||
Getting down to the meat of extending Evennia to make our game
|
||||
Part 4: `Using what we created <../Part4/Starting-Part4.html>`_
|
||||
Building a tech-demo and world content to go with our code
|
||||
Part 5: `Showing the world <../Part5/Starting-Part5.html>`_
|
||||
Taking our new game online and let players try it out
|
||||
|
||||
**Part 1: What we have**
|
||||
A tour of Evennia and how to use the tools, including an introduction to Python.
|
||||
Part 2: `What we want <../Part2/Starting-Part2.html>`_
|
||||
Planning our tutorial game and what to think about when planning your own in the future.
|
||||
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
|
||||
Getting down to the meat of extending Evennia to make our game
|
||||
Part 4: `Using what we created <../Part4/Starting-Part4.html>`_
|
||||
Building a tech-demo and world content to go with our code
|
||||
Part 5: `Showing the world <../Part5/Starting-Part5.html>`_
|
||||
Taking our new game online and let players try it out
|
||||
```
|
||||
|
||||
Welcome to Evennia! This multi-part Tutorial will help you get off the ground. It consists
|
||||
of five parts, each with several lessons. You can pick what seems interesting, but if you
|
||||
follow through to the end you will have created a little online game of your own to play
|
||||
and share with others!
|
||||
of five parts, each with several lessons. You can pick what seems interesting, but if you
|
||||
follow through to the end you will have created a little online game of your own to play
|
||||
and share with others!
|
||||
|
||||
## Lessons of Part 1 - "What we have"
|
||||
|
||||
1. Introduction (you are here)
|
||||
1. [Building stuff](./Building-Quickstart)
|
||||
1. [The Tutorial World](./Tutorial-World-Introduction)
|
||||
1. [Python basics](./Python-basic-introduction)
|
||||
1. [Game dir overview](./Gamedir-Overview)
|
||||
1. [Python classes and objects](./Python-classes-and-objects)
|
||||
1. [Accessing the Evennia library](./Evennia-Library-Overview)
|
||||
1. [Typeclasses and Persistent objects](./Learning-Typeclasses)
|
||||
1. [Making first own Commands](./Adding-Commands)
|
||||
1. [Parsing and replacing default Commands](./More-on-Commands)
|
||||
1. [Creating things](./Creating-Things)
|
||||
1. [Searching for things](./Searching-Things)
|
||||
1. [Advanced searching with Django queries](./Django-queries)
|
||||
1. [Building stuff](./Building-Quickstart.md)
|
||||
1. [The Tutorial World](./Tutorial-World-Introduction.md)
|
||||
1. [Python basics](./Python-basic-introduction.md)
|
||||
1. [Game dir overview](./Gamedir-Overview.md)
|
||||
1. [Python classes and objects](./Python-classes-and-objects.md)
|
||||
1. [Accessing the Evennia library](./Evennia-Library-Overview.md)
|
||||
1. [Typeclasses and Persistent objects](./Learning-Typeclasses.md)
|
||||
1. [Making first own Commands](./Adding-Commands.md)
|
||||
1. [Parsing and replacing default Commands](./More-on-Commands.md)
|
||||
1. [Creating things](./Creating-Things.md)
|
||||
1. [Searching for things](./Searching-Things.md)
|
||||
1. [Advanced searching with Django queries](./Django-queries.md)
|
||||
|
||||
In this first part we'll focus on what we get out of the box in Evennia - we'll get used to the tools,
|
||||
and how to find things we are looking for. We will also dive into some of things you'll
|
||||
and how to find things we are looking for. We will also dive into some of things you'll
|
||||
need to know to fully utilize the system, including giving you a brief rundown of Python concepts. If you are
|
||||
an experienced Python programmer, some sections may feel a bit basic, but you will at least not have seen
|
||||
these concepts in the context of Evennia before.
|
||||
an experienced Python programmer, some sections may feel a bit basic, but you will at least not have seen
|
||||
these concepts in the context of Evennia before.
|
||||
|
||||
## Things you will need
|
||||
## Things you will need
|
||||
|
||||
### A Command line
|
||||
### A Command line
|
||||
|
||||
First of all, you need to know how to find your Terminal/Console in your OS. The Evennia server can be controlled
|
||||
from in-game, but you _will_ need to use the command-line to get anywhere. Here are some starters:
|
||||
|
|
@ -55,75 +57,75 @@ from in-game, but you _will_ need to use the command-line to get anywhere. Here
|
|||
|
||||
### A MUD client
|
||||
|
||||
You might already have a MUD-client you prefer. Check out the [grid of supported clients](../../../Setup/Client-Support-Grid) for aid.
|
||||
If telnet's not your thing, you can also just use Evennia's web client in your browser.
|
||||
You might already have a MUD-client you prefer. Check out the [grid of supported clients](../../../Setup/Client-Support-Grid.md) for aid.
|
||||
If telnet's not your thing, you can also just use Evennia's web client in your browser.
|
||||
|
||||
> In this documentation we often use 'MUD' and 'MU' or 'MU*' interchangeably
|
||||
as labels to represent all the historically different forms of text-based multiplayer game-styles,
|
||||
as labels to represent all the historically different forms of text-based multiplayer game-styles,
|
||||
like MUD, MUX, MUSH, MUCK, MOO and others. Evennia can be used to create all those game-styles
|
||||
and more.
|
||||
|
||||
### An Editor
|
||||
You need a text-editor to edit Python source files. Most everything that can edit and output raw
|
||||
text works (so not Word).
|
||||
text works (so not Word).
|
||||
|
||||
- [Here's a blog post summing up some of the alternatives](https://www.elegantthemes.com/blog/resources/best-code-editors) - these
|
||||
things don't change much from year to year. Popular choices for Python are PyCharm, VSCode, Atom, Sublime Text and Notepad++.
|
||||
- [Here's a blog post summing up some of the alternatives](https://www.elegantthemes.com/blog/resources/best-code-editors) - these
|
||||
things don't change much from year to year. Popular choices for Python are PyCharm, VSCode, Atom, Sublime Text and Notepad++.
|
||||
Evennia is to a very large degree coded in VIM, but that's not suitable for beginners.
|
||||
|
||||
|
||||
> Hint: When setting up your editor, make sure that pressing TAB inserts _4 spaces_ rather than a Tab-character. Since
|
||||
> Python is whitespace-aware, this will make your life a lot easier.
|
||||
|
||||
|
||||
### Set up a game dir for the tutorial
|
||||
|
||||
Next you should make sure you have [installed Evennia](../../../Setup/Setup-Quickstart). If you followed the instructions
|
||||
you will already have created a game-dir. You could use that for this tutorial or you may want to do the
|
||||
Next you should make sure you have [installed Evennia](../../../Setup/Setup-Quickstart.md). If you followed the instructions
|
||||
you will already have created a game-dir. You could use that for this tutorial or you may want to do the
|
||||
tutorial in its own, isolated game dir; it's up to you.
|
||||
|
||||
- If you want a new gamedir for the tutorial game and already have Evennia running with another gamedir,
|
||||
- If you want a new gamedir for the tutorial game and already have Evennia running with another gamedir,
|
||||
first enter that gamedir and run
|
||||
|
||||
evennia stop
|
||||
|
||||
> If you want to run two parallel servers, that'd be fine too, but one would have to use
|
||||
evennia stop
|
||||
|
||||
> If you want to run two parallel servers, that'd be fine too, but one would have to use
|
||||
> different ports from the defaults, or there'd be a clash. We will go into changing settings later.
|
||||
- Now go to where you want to create your tutorial-game. We will always refer to it as `mygame` so
|
||||
it may be convenient if you do too:
|
||||
|
||||
|
||||
evennia --init mygame
|
||||
cd mygame
|
||||
evennia migrate
|
||||
cd mygame
|
||||
evennia migrate
|
||||
evennia start --log
|
||||
|
||||
Add your superuser name and password at the prompt (email is optional). Make sure you can
|
||||
|
||||
Add your superuser name and password at the prompt (email is optional). Make sure you can
|
||||
go to `localhost:4000` in your MUD client or to [http://localhost:4001](http://localhost:4001)
|
||||
in your web browser (Mac users: Try `127.0.0.1` instead of `localhost` if you have trouble).
|
||||
|
||||
|
||||
The above `--log` flag will have Evennia output all its logs to the terminal. This will block
|
||||
the terminal from other input. To leave the log-view, press `Ctrl-C` (`Cmd-C` on Mac). To see
|
||||
the log again just run
|
||||
|
||||
the log again just run
|
||||
|
||||
evennia --log
|
||||
|
||||
You should now be good to go!
|
||||
|
||||
|
||||
```toctree::
|
||||
:hidden:
|
||||
|
||||
Building-Quickstart
|
||||
Tutorial-World-Introduction
|
||||
Python-basic-introduction
|
||||
Gamedir-Overview
|
||||
Python-classes-and-objects
|
||||
Evennia-Library-Overview
|
||||
Learning-Typeclasses
|
||||
Adding-Commands
|
||||
More-on-Commands
|
||||
Creating-Things
|
||||
Searching-Things
|
||||
Django-queries
|
||||
../Part2/Starting-Part2
|
||||
You should now be good to go!
|
||||
|
||||
```
|
||||
|
||||
```{toctree}
|
||||
:hidden:
|
||||
|
||||
Building-Quickstart
|
||||
Tutorial-World-Introduction
|
||||
Python-basic-introduction
|
||||
Gamedir-Overview
|
||||
Python-classes-and-objects
|
||||
Evennia-Library-Overview
|
||||
Learning-Typeclasses
|
||||
Adding-Commands
|
||||
More-on-Commands
|
||||
Creating-Things
|
||||
Searching-Things
|
||||
Django-queries
|
||||
../Part2/Starting-Part2
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
# The Tutorial World
|
||||
# The Tutorial World
|
||||
|
||||
The *Tutorial World* is a small and functioning MUD-style game world shipped with Evennia.
|
||||
The *Tutorial World* is a small and functioning MUD-style game world shipped with Evennia.
|
||||
It's a small showcase of what is possible. It can also be useful for those who have an easier
|
||||
time learning by deconstructing existing code.
|
||||
time learning by deconstructing existing code.
|
||||
|
||||
Stand in the Limbo room and install it with
|
||||
Stand in the Limbo room and install it with
|
||||
|
||||
batchcommand tutorial_world.build
|
||||
|
||||
|
||||
What this does is to run the build script
|
||||
[evennia/contrib/tutorial_world/build.ev](github:evennia/contrib/tutorial_world/build.ev).
|
||||
This is pretty much just a list of build-commands executed in sequence by the `batchcommand` command.
|
||||
This is pretty much just a list of build-commands executed in sequence by the `batchcommand` command.
|
||||
Wait for the building to complete and don't run it twice. A new exit should have appeared named _Tutorial_.
|
||||
|
||||
The game consists of a single-player quest and has some 20 rooms that you can explore as you seek
|
||||
|
||||
The game consists of a single-player quest and has some 20 rooms that you can explore as you seek
|
||||
to discover the whereabouts of a mythical weapon. Make sure you don't play as superuser:
|
||||
|
||||
quell
|
||||
|
||||
Enter the new exit by writing `tutorial`. Enjoy! If you succeed you will eventually
|
||||
|
||||
Enter the new exit by writing `tutorial`. Enjoy! If you succeed you will eventually
|
||||
end up back in Limbo.
|
||||
|
||||
|
||||
## Gameplay
|
||||
|
||||

|
||||
|
|
@ -39,7 +39,7 @@ face you stand where the moor meets the sea along a high, rocky coast ...*
|
|||
- Look at everything. While a demo, this is not necessarily trivial, depending on your experience with
|
||||
text-based adventure games. Just remember that everything can be solved or bypassed.
|
||||
- Some things cannot be damaged by mortal weapons. In that case it's OK to run away. Expect
|
||||
to be chased though.
|
||||
to be chased though.
|
||||
- Some objects are interactive in more than one way. Use the normal `help` command to get a feel for
|
||||
which commands are available at any given time.
|
||||
- Use the command `tutorial` to get insight behind the scenes of the game.
|
||||
|
|
@ -49,14 +49,14 @@ which commands are available at any given time.
|
|||
- *defend* will lower the chance to taking damage on your enemy's next attack.
|
||||
- Being defeated is a part of the experience. You can't actually die, but getting knocked out
|
||||
means being left in the dark ...
|
||||
|
||||
|
||||
## Once you are done (or had enough)
|
||||
|
||||
Afterwards you'll either have conquered the old ruin and returned in glory and triumph ... or
|
||||
Afterwards you'll either have conquered the old ruin and returned in glory and triumph ... or
|
||||
you returned limping and whimpering from the challenge through `telport limbo`.
|
||||
Either way you should now be back in Limbo, able to reflect on the experience.
|
||||
|
||||
Some features exemplified by the tutorial world:
|
||||
Some features exemplified by the tutorial world:
|
||||
|
||||
- Rooms with custom ability to show details (like looking at the wall in the dark room)
|
||||
- Hidden or impassable exits until you fulfilled some criterion
|
||||
|
|
@ -72,14 +72,14 @@ Some features exemplified by the tutorial world:
|
|||
- Object spawning (the weapons in the barrel and the final weapoon is actually randomized)
|
||||
- Teleporter trap rooms (if you fail the obelisk puzzle)
|
||||
|
||||
```sidebar:: Extra Credit
|
||||
```{sidebar} Extra Credit
|
||||
|
||||
If you have previous programming experience (or after you have gone
|
||||
through this Starter tutorial) it may be instructive to dig a little deeper into the Tutorial-world
|
||||
code to learn how it achieves what it does. The code is heavily documented.
|
||||
If you have previous programming experience (or after you have gone
|
||||
through this Starter tutorial) it may be instructive to dig a little deeper into the Tutorial-world
|
||||
code to learn how it achieves what it does. The code is heavily documented.
|
||||
You can find all the code in `evennia/contrib/tutorial_world <../../api/evennia.contrib.tutorial_world.html>`_,
|
||||
the build-script is `here <https://github.com/evennia/evennia/blob/master/evennia/contrib/tutorial_world/build.ev>`_.
|
||||
|
||||
|
||||
|
||||
When reading and learning from the code, however, keep in mind that *Tutorial World* was created with a very
|
||||
specific goal in mind: to install easily and to not permanently modify the rest of the server. It therefore
|
||||
|
|
@ -87,7 +87,7 @@ Some features exemplified by the tutorial world:
|
|||
you will usually need to worry about when making your own game.
|
||||
```
|
||||
|
||||
Quite a lot of stuff crammed in such a small area!
|
||||
Quite a lot of stuff crammed in such a small area!
|
||||
|
||||
## Uninstall the tutorial world
|
||||
|
||||
|
|
|
|||
|
|
@ -1,55 +1,55 @@
|
|||
# On Planning a Game
|
||||
|
||||
Last lesson we asked ourselves some questions about our motivation. In this one we'll present
|
||||
some more technical questions to consider. In the next lesson we'll answer them for the sake of
|
||||
Last lesson we asked ourselves some questions about our motivation. In this one we'll present
|
||||
some more technical questions to consider. In the next lesson we'll answer them for the sake of
|
||||
our tutorial game.
|
||||
|
||||
Note that the suggestions on this page are just that - suggestions. Also, they are primarily aimed at a lone
|
||||
hobby designer or a small team developing a game in their free time.
|
||||
Note that the suggestions on this page are just that - suggestions. Also, they are primarily aimed at a lone
|
||||
hobby designer or a small team developing a game in their free time.
|
||||
|
||||
```important::
|
||||
```{important}
|
||||
|
||||
Your first all overshadowing goal is to beat the odds and get **something** out the door!
|
||||
Even if it's a scaled-down version of your dream game, lacking many "must-have" features!
|
||||
Your first all overshadowing goal is to beat the odds and get **something** out the door!
|
||||
Even if it's a scaled-down version of your dream game, lacking many "must-have" features!
|
||||
|
||||
```
|
||||
|
||||
Remember: *99.99999% of all great game ideas never lead to a game*. Especially not to an online
|
||||
game that people can actually play and enjoy. It's better to get your game out there and expand on it
|
||||
game that people can actually play and enjoy. It's better to get your game out there and expand on it
|
||||
later than to code in isolation until you burn out, lose interest or your hard drive crashes.
|
||||
|
||||
- Keep the scope of your initial release down. Way down.
|
||||
- Keep the scope of your initial release down. Way down.
|
||||
- Start small, with an eye towards expansions later, after first release.
|
||||
- If the suggestions here seems boring or a chore to you, do it your way instead. Everyone's different.
|
||||
- Keep having _fun_. You must keep your motivation up, whichever way works for _you_.
|
||||
- If the suggestions here seems boring or a chore to you, do it your way instead. Everyone's different.
|
||||
- Keep having _fun_. You must keep your motivation up, whichever way works for _you_.
|
||||
|
||||
|
||||
## The steps
|
||||
## The steps
|
||||
|
||||
Here are the rough steps towards your goal.
|
||||
|
||||
1. Planning
|
||||
1. Planning
|
||||
2. Coding + Gradually building a tech-demo
|
||||
3. Building the actual game world
|
||||
4. Release
|
||||
5. Celebrate
|
||||
|
||||
## Planning
|
||||
## Planning
|
||||
|
||||
You need to have at least a rough idea about what you want to create. Some like a lot of planning, others
|
||||
do it more seat-of-the-pants style. Regardless, while _some_ planning is always good to do, it's common
|
||||
to have your plans change on you as you create your code prototypes. So don't get _too_ bogged down in
|
||||
You need to have at least a rough idea about what you want to create. Some like a lot of planning, others
|
||||
do it more seat-of-the-pants style. Regardless, while _some_ planning is always good to do, it's common
|
||||
to have your plans change on you as you create your code prototypes. So don't get _too_ bogged down in
|
||||
the details out of the gate.
|
||||
|
||||
Many prospective game developers are very good at *parts* of this process, namely in defining what their
|
||||
world is "about": The theme, the world concept, cool monsters and so on. Such things are very important. But
|
||||
unfortunately, they are not enough to make your game. You need to figure out how to accomplish your ideas in
|
||||
Many prospective game developers are very good at *parts* of this process, namely in defining what their
|
||||
world is "about": The theme, the world concept, cool monsters and so on. Such things are very important. But
|
||||
unfortunately, they are not enough to make your game. You need to figure out how to accomplish your ideas in
|
||||
Evennia.
|
||||
|
||||
Below are some questions to get you going. In the next lesson we will try to answer them for our particular
|
||||
tutorial game. There are of course many more questions you could be asking yourself.
|
||||
|
||||
### Administration
|
||||
### Administration
|
||||
|
||||
- Should your game rules be enforced by coded systems or by human game masters?
|
||||
- What is the staff hierarchy in your game? Is vanilla Evennia roles enough or do you need something else?
|
||||
|
|
@ -57,21 +57,21 @@ tutorial game. There are of course many more questions you could be asking yours
|
|||
|
||||
### Building
|
||||
|
||||
- How will the world be built? Traditionally (from in-game with build-commands) or externally (by batchcmds/code
|
||||
or directly with custom code)?
|
||||
- How will the world be built? Traditionally (from in-game with build-commands) or externally (by batchcmds/code
|
||||
or directly with custom code)?
|
||||
- Can only privileged Builders create things or should regular players also have limited build-capability?
|
||||
|
||||
### Systems
|
||||
|
||||
- Do you base your game off an existing RPG system or make up your own?
|
||||
- What are the game mechanics? How do you decide if an action succeeds or fails?
|
||||
- What are the game mechanics? How do you decide if an action succeeds or fails?
|
||||
- Does the flow of time matter in your game - does night and day change? What about seasons?
|
||||
- Do you want changing, global weather or should weather just be set manually in roleplay?
|
||||
- Do you want changing, global weather or should weather just be set manually in roleplay?
|
||||
- Do you want a coded world-economy or just a simple barter system? Or no formal economy at all?
|
||||
- Do you have concepts like reputation and influence?
|
||||
- Will your characters be known by their name or only by their physical appearance?
|
||||
- Do you have concepts like reputation and influence?
|
||||
- Will your characters be known by their name or only by their physical appearance?
|
||||
|
||||
### Rooms
|
||||
### Rooms
|
||||
|
||||
- Is a simple room description enough or should the description be able to change (such as with time, by
|
||||
light conditions, weather or season)?
|
||||
|
|
@ -91,60 +91,60 @@ created on demand?
|
|||
- Can you fight with a chair or a flower or must you use a specific 'weapon' kind of thing?
|
||||
- Will characters be able to craft new objects?
|
||||
- Should mobs/NPCs have some sort of AI?
|
||||
- Are NPCs and mobs different entities? How do they differ?
|
||||
- Should there be NPCs giving quests? If so, how do you track Quest status?
|
||||
- Are NPCs and mobs different entities? How do they differ?
|
||||
- Should there be NPCs giving quests? If so, how do you track Quest status?
|
||||
|
||||
### Characters
|
||||
|
||||
- Can players have more than one Character active at a time or are they allowed to multi-play?
|
||||
- How does the character-generation work? Walk from room-to-room? A menu?
|
||||
- How does the character-generation work? Walk from room-to-room? A menu?
|
||||
- How do you implement different "classes" or "races"? Are they separate types of objects or do you
|
||||
simply load different stats on a basic object depending on what the Player wants?
|
||||
- If a Character can hide in a room, what skill will decide if they are detected?
|
||||
- What does the skill tree look like? Can a Character gain experience to improve? By killing
|
||||
enemies? Solving quests? By roleplaying?
|
||||
- May player-characters attack each other (PvP)?
|
||||
- What are the penalties of defeat? Permanent death? Quick respawn? Time in prison?
|
||||
- What are the penalties of defeat? Permanent death? Quick respawn? Time in prison?
|
||||
|
||||
A MUD's a lot more involved than you would think and these things hang together in a complex web. It
|
||||
can easily become overwhelming and it's tempting to want *all* functionality right out of the door.
|
||||
Try to identify the basic things that "make" your game and focus *only* on them for your first
|
||||
release. Make a list. Keep future expansions in mind but limit yourself.
|
||||
|
||||
## Coding and Tech demo
|
||||
## Coding and Tech demo
|
||||
|
||||
This is the actual work of creating the "game" part of your game. As you code and test systems you should
|
||||
build a little "tech demo" along the way.
|
||||
This is the actual work of creating the "game" part of your game. As you code and test systems you should
|
||||
build a little "tech demo" along the way.
|
||||
|
||||
```sidebar:: Tech demo
|
||||
```{sidebar} Tech demo
|
||||
|
||||
With "tech demo" we mean a small example of your code in-action: A room with a mob,
|
||||
a way to jump into and test character-creation etc. The tech demo need not be pretty, it's
|
||||
a way to jump into and test character-creation etc. The tech demo need not be pretty, it's
|
||||
there to test functionality. It's not the beginning of your game world (unless you find that
|
||||
to be more fun).
|
||||
|
||||
```
|
||||
|
||||
Try to avoid going wild with building a huge game world before you have a tech-demo showing off all parts
|
||||
Try to avoid going wild with building a huge game world before you have a tech-demo showing off all parts
|
||||
you expect to have in the first version of your game. Otherwise you run the risk of having to redo it all
|
||||
again.
|
||||
again.
|
||||
|
||||
Evennia tries hard to make the coding easier for you, but there is no way around the fact that if you want
|
||||
anything but a basic chat room you *will* have to bite the bullet and code your game (or find a coder willing
|
||||
Evennia tries hard to make the coding easier for you, but there is no way around the fact that if you want
|
||||
anything but a basic chat room you *will* have to bite the bullet and code your game (or find a coder willing
|
||||
to do it for you).
|
||||
|
||||
> Even if you won't code anything yourself, as a designer you need to at least understand the basic
|
||||
paradigms and components of Evennia. It's recommended you look over the rest of this Beginner Tutorial to learn
|
||||
what tools you have available.
|
||||
what tools you have available.
|
||||
|
||||
During Coding you look back at the things you wanted during the **Planning** phase and try to
|
||||
implement them. Don't be shy to update your plans if you find things easier/harder than you thought.
|
||||
The earlier you revise problems, the easier they will be to fix.
|
||||
|
||||
A good idea is to host your code online using _version control_. Github.com offers free Private repos
|
||||
A good idea is to host your code online using _version control_. Github.com offers free Private repos
|
||||
these days if you don't want the world to learn your secrets. Not only version control
|
||||
make it easy for your team to collaborate, it also means
|
||||
your work is backed up at all times. The page on [Version Control](../../../Coding/Version-Control)
|
||||
your work is backed up at all times. The page on [Version Control](../../../Coding/Version-Control.md)
|
||||
will help you to setting up a sane developer environment with proper version control.
|
||||
|
||||
## World Building
|
||||
|
|
@ -154,17 +154,17 @@ populating the database with a larger, thematic world. Too many would-be develop
|
|||
stage too soon (skipping the **Coding** or even **Planning** stages). What if the rooms you build
|
||||
now doesn't include all the nice weather messages the code grows to support? Or the way you store
|
||||
data changes under the hood? Your building work would at best require some rework and at worst you
|
||||
would have to redo the whole thing. You could be in for a *lot* of unnecessary work if you build stuff
|
||||
would have to redo the whole thing. You could be in for a *lot* of unnecessary work if you build stuff
|
||||
en masse without having the underlying code systems in some reasonable shape first.
|
||||
|
||||
So before starting to build, the "game" bit (**Coding** + **Testing**) should be more or less
|
||||
**complete**, *at least to the level of your initial release*.
|
||||
|
||||
Make sure it is clear to yourself and your eventual builders just which parts of the world you want
|
||||
for your initial release. Establish for everyone which style, quality and level of detail you expect.
|
||||
Make sure it is clear to yourself and your eventual builders just which parts of the world you want
|
||||
for your initial release. Establish for everyone which style, quality and level of detail you expect.
|
||||
|
||||
Your goal should *not* be to complete your entire world in one go. You want just enough to make the
|
||||
game's "feel" come across. You want a minimal but functioning world where the intended game play can
|
||||
Your goal should *not* be to complete your entire world in one go. You want just enough to make the
|
||||
game's "feel" come across. You want a minimal but functioning world where the intended game play can
|
||||
be tested and roughly balanced. You can always add new areas later.
|
||||
|
||||
During building you get free and extensive testing of whatever custom build commands and systems you
|
||||
|
|
@ -176,18 +176,18 @@ to this feedback.
|
|||
## Alpha Release
|
||||
|
||||
As mentioned, don't hold onto your world more than necessary. *Get it out there* with a huge *Alpha*
|
||||
flag and let people try it!
|
||||
flag and let people try it!
|
||||
|
||||
Call upon your alpha-players to try everything - they *will* find ways to break your game in ways that
|
||||
Call upon your alpha-players to try everything - they *will* find ways to break your game in ways that
|
||||
you never could have imagined. In Alpha you might be best off to
|
||||
focus on inviting friends and maybe other MUD developers, people who you can pester to give proper
|
||||
feedback and bug reports (there *will* be bugs, there is no way around it).
|
||||
feedback and bug reports (there *will* be bugs, there is no way around it).
|
||||
|
||||
Follow the quick instructions for [Online Setup](../../../Setup/Online-Setup) to make your
|
||||
game visible online.
|
||||
Follow the quick instructions for [Online Setup](../../../Setup/Online-Setup.md) to make your
|
||||
game visible online.
|
||||
|
||||
If you hadn't already, make sure to put up your game on the
|
||||
[Evennia game index](http://games.evennia.com/) so people know it's in the works (actually, even
|
||||
If you hadn't already, make sure to put up your game on the
|
||||
[Evennia game index](http://games.evennia.com/) so people know it's in the works (actually, even
|
||||
pre-alpha games are allowed in the index so don't be shy)!
|
||||
|
||||
## Beta Release/Perpetual Beta
|
||||
|
|
@ -198,12 +198,12 @@ Once things stabilize in Alpha you can move to *Beta* and let more people in. Ma
|
|||
features get implemented or Players come with suggestions. As the game designer it is now up to you
|
||||
to gradually perfect your vision.
|
||||
|
||||
## Congratulate yourself!
|
||||
## Congratulate yourself!
|
||||
|
||||
You are worthy of a celebration since at this point you have joined the small, exclusive crowd who
|
||||
have made their dream game a reality!
|
||||
|
||||
## Planning our tutorial game
|
||||
|
||||
In the next lesson we'll make use of these general points and try to plan out our tutorial game.
|
||||
In the next lesson we'll make use of these general points and try to plan out our tutorial game.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ That said, Evennia _does_ offer some more game-opinionated _optional_ stuff. The
|
|||
and is an ever-growing treasure trove of code snippets, concepts and even full systems you can pick and choose
|
||||
from to use, tweak or take inspiration from when you make your game.
|
||||
|
||||
The [Contrib overview](../../../Contribs/Contrib-Overview) page gives the full list of the current roster of contributions. On
|
||||
The [Contrib overview](../../../Contribs/Contrib-Overview.md) page gives the full list of the current roster of contributions. On
|
||||
this page we will review a few contribs we will make use of for our game. We will do the actual installation
|
||||
of them when we start coding in the next part of this tutorial series. While we will introduce them here, you
|
||||
are wise to read their doc-strings yourself for the details.
|
||||
|
|
@ -26,7 +26,7 @@ This is the things we know we need:
|
|||
|
||||
## Barter contrib
|
||||
|
||||
[source](api:evennia.contrib.barter)
|
||||
[source](../../../api/evennia.contrib.barter.md)
|
||||
|
||||
Reviewing this contrib suggests that it allows for safe trading between two parties. The basic principle
|
||||
is that the parties puts up the stuff they want to sell and the system will guarantee that these systems are
|
||||
|
|
@ -57,7 +57,7 @@ things than boring gold coin.
|
|||
|
||||
## Character generation contrib
|
||||
|
||||
[source](api:evennia.contrib.chargen)
|
||||
[source](../../../api/evennia.contrib.chargen.md)
|
||||
|
||||
This contrib is an example module for creating characters. Since we will be using `MULTISESSION_MODE=3` we will
|
||||
get a selection screen like this automatically. We also plan to use a proper menu to build our character, so
|
||||
|
|
@ -65,7 +65,7 @@ we will _not_ be using this contrib.
|
|||
|
||||
## Clothing contrib
|
||||
|
||||
[source](api:evennia.contrib.clothing)
|
||||
[source](../../../api/evennia.contrib.clothing.md)
|
||||
|
||||
This contrib provides a full system primarily aimed at wearing clothes, but it could also work for armor. You wear
|
||||
an object in a particular location and this will then be reflected in your character's description. You can
|
||||
|
|
@ -80,7 +80,7 @@ for things like armor. It's a good contrib to build from though, so that's what
|
|||
|
||||
## Dice contrib
|
||||
|
||||
[source](api:evennia.contrib.dice)
|
||||
[source](../../../api/evennia.contrib.dice.md)
|
||||
|
||||
The dice contrib presents a general dice roller to use in game.
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ or play a game, we will not need it for the core of our game.
|
|||
|
||||
## Extended room contrib
|
||||
|
||||
[source](api:evennia.contrib.extended_room)
|
||||
[source](../../../api/evennia.contrib.extended_room.md)
|
||||
|
||||
This is a custom Room typeclass that changes its description based on time of day and season.
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ game, why not!
|
|||
|
||||
## RP-System contrib
|
||||
|
||||
[source](api:evennia.contrib.rpsystem)
|
||||
[source](../../../api/evennia.contrib.rpsystem.md)
|
||||
|
||||
This contrib adds a full roleplaying subsystem to your game. It gives every character a "short-description"
|
||||
(sdesc) that is what people will see when first meeting them. Let's say Tom has an sdesc "A tall man" and
|
||||
|
|
@ -159,20 +159,20 @@ You can also wear a mask to hide your identity; your sdesc will then be changed
|
|||
like `a person with a mask`.
|
||||
|
||||
The RPSystem gives a lot of roleplaying power out of the box, so we will add it. There is also a separate
|
||||
[rplanguage](api:evennia.contrib.rplanguage) module that integrates with the spoken words in your emotes and garbles them if you don't understand
|
||||
[rplanguage](../../../api/evennia.contrib.rplanguage.md) module that integrates with the spoken words in your emotes and garbles them if you don't understand
|
||||
the language spoken. In order to restrict the scope we will not include languages for the tutorial game.
|
||||
|
||||
|
||||
## Talking NPC contrib
|
||||
|
||||
[source](api:evennia.contrib.talking_npc)
|
||||
[source](../../../api/evennia.contrib.talking_npc.md)
|
||||
|
||||
This exemplifies an NPC with a menu-driven dialogue tree. We will not use this contrib explicitly, but it's
|
||||
good as inspiration for how we'll do quest-givers later.
|
||||
|
||||
## Traits contrib
|
||||
|
||||
[source](api:evennia.contrib.traits)
|
||||
[source](../../../api/evennia.contrib.traits.md)
|
||||
|
||||
An issue with dealing with roleplaying attributes like strength, dexterity, or skills like hunting, sword etc
|
||||
is how to keep track of the values in the moment. Your strength may temporarily be buffed by a strength-potion.
|
||||
|
|
@ -226,7 +226,7 @@ Traits will be very practical to use for our character sheets.
|
|||
|
||||
## Turnbattle contrib
|
||||
|
||||
[source](api:evennia.contrib.turnbattle)
|
||||
[source](../../../api/evennia.contrib.turnbattle.md)
|
||||
|
||||
This contrib consists of several implementations of a turn-based combat system, divivided into complexity:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,53 +1,53 @@
|
|||
# Planning our tutorial game
|
||||
|
||||
Using the general plan from last lesson we'll now establish what kind of game we want to create for this tutorial.
|
||||
Remembering that we need to keep the scope down, let's establish some parameters.
|
||||
Note that for your own
|
||||
game you don't _need_ to agree/adopt any of these. Many game-types need more or much less than this.
|
||||
Remembering that we need to keep the scope down, let's establish some parameters.
|
||||
Note that for your own
|
||||
game you don't _need_ to agree/adopt any of these. Many game-types need more or much less than this.
|
||||
But this makes for good, instructive examples.
|
||||
|
||||
- To have something to refer to rather than just saying "our tutorial game" over and over, we'll
|
||||
- To have something to refer to rather than just saying "our tutorial game" over and over, we'll
|
||||
name it ... _EvAdventure_.
|
||||
- We want EvAdventure be a small game we can play ourselves for fun, but which could in principle be expanded
|
||||
- We want EvAdventure be a small game we can play ourselves for fun, but which could in principle be expanded
|
||||
to something more later.
|
||||
- Let's go with a fantasy theme, it's well understood.
|
||||
- We'll use some existing, simple RPG system.
|
||||
- We want to be able to create and customize a character of our own.
|
||||
- We want the tools to roleplay with other players.
|
||||
- We'll use some existing, simple RPG system.
|
||||
- We want to be able to create and customize a character of our own.
|
||||
- We want the tools to roleplay with other players.
|
||||
- We don't want to have to rely on a Game master to resolve things, but will rely on code for skill resolution
|
||||
and combat.
|
||||
- We want monsters to fight and NPCs we can talk to. So some sort of AI.
|
||||
- We want to be able to buy and sell stuff, both with NPCs and other players.
|
||||
- We want monsters to fight and NPCs we can talk to. So some sort of AI.
|
||||
- We want to be able to buy and sell stuff, both with NPCs and other players.
|
||||
- We want some sort of crafting system.
|
||||
- We want some sort of quest system.
|
||||
- We want some sort of quest system.
|
||||
|
||||
Let's answer the questions from the previous lesson and discuss some of the possibilities.
|
||||
Let's answer the questions from the previous lesson and discuss some of the possibilities.
|
||||
|
||||
### Administration
|
||||
## Administration
|
||||
|
||||
#### Should your game rules be enforced by coded systems by human game masters?
|
||||
### Should your game rules be enforced by coded systems by human game masters?
|
||||
|
||||
Generally, the more work you expect human staffers/GMs to do, the less your code needs to work. To
|
||||
support GMs you'd need to design commands to support GM-specific actions and the type of game-mastering
|
||||
you want them to do. You may need to expand communication channels so you can easily
|
||||
Generally, the more work you expect human staffers/GMs to do, the less your code needs to work. To
|
||||
support GMs you'd need to design commands to support GM-specific actions and the type of game-mastering
|
||||
you want them to do. You may need to expand communication channels so you can easily
|
||||
talk to groups people in private and split off gaming groups from each other. RPG rules could be as simple
|
||||
as the GM sitting with the rule books and using a dice-roller for visibility.
|
||||
|
||||
GM:ing is work-intensive however, and even the most skilled and enthusiastic GM can't be awake all hours
|
||||
GM:ing is work-intensive however, and even the most skilled and enthusiastic GM can't be awake all hours
|
||||
of the day to serve an international player base. The computer never needs sleep, so having the ability for
|
||||
players to "self-serve" their RP itch when no GMs are around is a good idea even for the most GM-heavy games.
|
||||
|
||||
On the other side of the spectrum are games with no GMs at all; all gameplay are driven either by the computer
|
||||
or by the interactions between players. Such games still need an active staff, but nowhere as much active
|
||||
or by the interactions between players. Such games still need an active staff, but nowhere as much active
|
||||
involvement. Allowing for solo-play with the computer also allows players to have fun when the number of active
|
||||
players is low.
|
||||
players is low.
|
||||
|
||||
We want EvAdventure to work entirely without depending on human GMs. That said, there'd be nothing
|
||||
stopping a GM from stepping in and run an adventure for some players should they want to.
|
||||
We want EvAdventure to work entirely without depending on human GMs. That said, there'd be nothing
|
||||
stopping a GM from stepping in and run an adventure for some players should they want to.
|
||||
|
||||
#### What is the staff hierarchy in your game? Is vanilla Evennia roles enough or do you need something else?
|
||||
### What is the staff hierarchy in your game? Is vanilla Evennia roles enough or do you need something else?
|
||||
|
||||
The default hierarchy is
|
||||
The default hierarchy is
|
||||
|
||||
- `Player` - regular players
|
||||
- `Player Helper` - can create/edit help entries
|
||||
|
|
@ -58,368 +58,368 @@ The default hierarchy is
|
|||
There is also the _superuser_, the "owner" of the game you create when you first set up your database. This user
|
||||
goes outside the regular hierarchy and should usually only.
|
||||
|
||||
We are okay with keeping this structure for our game.
|
||||
We are okay with keeping this structure for our game.
|
||||
|
||||
#### Should players be able to post out-of-characters on channels and via other means like bulletin-boards?
|
||||
|
||||
Evennia's _Channels_ are by default only available between _Accounts_. That is, for players to communicate with each
|
||||
### Should players be able to post out-of-characters on channels and via other means like bulletin-boards?
|
||||
|
||||
Evennia's _Channels_ are by default only available between _Accounts_. That is, for players to communicate with each
|
||||
other. By default, the `public` channel is created for general discourse.
|
||||
Channels are logged to a file and when you are coming back to the game you can view the history of a channel
|
||||
in case you missed something.
|
||||
in case you missed something.
|
||||
|
||||
> public Hello world!
|
||||
[Public] MyName: Hello world!
|
||||
[Public] MyName: Hello world!
|
||||
|
||||
But Channels can also be set up to work between Characters instead of Accounts. This would mean the channels
|
||||
would have an in-game meaning:
|
||||
would have an in-game meaning:
|
||||
|
||||
- Members of a guild could be linked telepathically.
|
||||
- Members of a guild could be linked telepathically.
|
||||
- Survivors of the apocalypse can communicate over walkie-talkies.
|
||||
- Radio stations you can tune into or have to discover.
|
||||
- Radio stations you can tune into or have to discover.
|
||||
|
||||
_Bulletin boards_ are a sort of in-game forum where posts are made publicly or privately. Contrary to a channel,
|
||||
the messages are usually stored and are grouped into topics with replies. Evennia has no default bulletin-board
|
||||
system.
|
||||
system.
|
||||
|
||||
In EvAdventure we will just use the default inter-account channels. We will also not be implementing any
|
||||
bulletin boards.
|
||||
In EvAdventure we will just use the default inter-account channels. We will also not be implementing any
|
||||
bulletin boards.
|
||||
|
||||
### Building
|
||||
## Building
|
||||
|
||||
#### How will the world be built?
|
||||
|
||||
There are two main ways to handle this:
|
||||
### How will the world be built?
|
||||
|
||||
There are two main ways to handle this:
|
||||
- Traditionally, from in-game with build-commands: This means builders creating content in their game
|
||||
client. This has the advantage of not requiring Python skills nor server access. This can often be a quite
|
||||
intuitive way to build since you are sort-of walking around in your creation as you build it. However, the
|
||||
developer (you) must make sure to provide build-commands that are flexible enough for builders to be able to
|
||||
create the content you want for your game.
|
||||
- Externally (by batchcmds): Evennia's `batchcmd` takes a text file with Evennia Commands and executes them
|
||||
in sequence. This allows the build process to be repeated and applied quickly to a new database during development.
|
||||
client. This has the advantage of not requiring Python skills nor server access. This can often be a quite
|
||||
intuitive way to build since you are sort-of walking around in your creation as you build it. However, the
|
||||
developer (you) must make sure to provide build-commands that are flexible enough for builders to be able to
|
||||
create the content you want for your game.
|
||||
- Externally (by batchcmds): Evennia's `batchcmd` takes a text file with Evennia Commands and executes them
|
||||
in sequence. This allows the build process to be repeated and applied quickly to a new database during development.
|
||||
It also allows builders to use proper text-editing tools rather than writing things line-by-line in their clients.
|
||||
The drawback is that for their changes to go live they either need server access or they need to send their
|
||||
batchcode to the game administrator so they can apply the changes. Or use version control.
|
||||
- Externally (with batchcode or custom code): This is the "professional game development" approach. This gives the
|
||||
builders maximum power by creating the content in Python using Evennia primitives. The `batchcode` processor
|
||||
allows Evennia to apply and re-apply build-scripts that are raw Python modules. Again, this would require the
|
||||
The drawback is that for their changes to go live they either need server access or they need to send their
|
||||
batchcode to the game administrator so they can apply the changes. Or use version control.
|
||||
- Externally (with batchcode or custom code): This is the "professional game development" approach. This gives the
|
||||
builders maximum power by creating the content in Python using Evennia primitives. The `batchcode` processor
|
||||
allows Evennia to apply and re-apply build-scripts that are raw Python modules. Again, this would require the
|
||||
builder to have server access or to use version control to share their work with the rest of the development team.
|
||||
|
||||
In this tutorial, we will show examples of all these ways, but since we don't have a team of builders we'll
|
||||
build the brunt of things using Evennia's Batchcode system.
|
||||
In this tutorial, we will show examples of all these ways, but since we don't have a team of builders we'll
|
||||
build the brunt of things using Evennia's Batchcode system.
|
||||
|
||||
#### Can only privileged Builders create things or should regular players also have limited build-capability?
|
||||
### Can only privileged Builders create things or should regular players also have limited build-capability?
|
||||
|
||||
In some game styles, players have the ability to create objects and even script them. While giving regular users
|
||||
the ability to create objects with in-built commands is easy and safe, actual code-creation (aka _softcode_ ) is
|
||||
not something Evennia supports natively. Regular, untrusted users should never be allowed to execute raw Python
|
||||
code (such as what you can do with the `py` command). You can
|
||||
[read more about Evennia's stance on softcode here](../../../Concepts/Soft-Code). If you want users to do limited scripting,
|
||||
it's suggested that this is accomplished by adding more powerful build-commands for them to use.
|
||||
the ability to create objects with in-built commands is easy and safe, actual code-creation (aka _softcode_ ) is
|
||||
not something Evennia supports natively. Regular, untrusted users should never be allowed to execute raw Python
|
||||
code (such as what you can do with the `py` command). You can
|
||||
[read more about Evennia's stance on softcode here](../../../Concepts/Soft-Code.md). If you want users to do limited scripting,
|
||||
it's suggested that this is accomplished by adding more powerful build-commands for them to use.
|
||||
|
||||
For our tutorial-game, we will only allow privileged builders to modify the world. The exception is crafting,
|
||||
For our tutorial-game, we will only allow privileged builders to modify the world. The exception is crafting,
|
||||
which we will limit to repairing broken items by combining them with other repair-related items.
|
||||
|
||||
### Systems
|
||||
## Systems
|
||||
|
||||
#### Do you base your game off an existing RPG system or make up your own?
|
||||
### Do you base your game off an existing RPG system or make up your own?
|
||||
|
||||
We will make use of [Open Adventure](http://www.geekguild.com/openadventure/), a simple 'old school' RPG-system
|
||||
that is available for free under the Creative Commons license. We'll only use a subset of the rules from
|
||||
We will make use of [Open Adventure](http://www.geekguild.com/openadventure/), a simple 'old school' RPG-system
|
||||
that is available for free under the Creative Commons license. We'll only use a subset of the rules from
|
||||
the blue "basic" book. For the sake of keeping down the length of this tutorial we will limit what features
|
||||
we will include:
|
||||
we will include:
|
||||
|
||||
- Only two 'archetypes' (classes) - Arcanist (wizard) and Warrior, these are examples of two different play
|
||||
styles.
|
||||
- Two races only (dwarves and elves), to show off how to implement races and race bonuses.
|
||||
- No extra features of the races/archetypes such as foci and special feats. While these are good for fleshing
|
||||
- No extra features of the races/archetypes such as foci and special feats. While these are good for fleshing
|
||||
out a character, these will work the same as other bonuses and are thus not that instructive.
|
||||
- We will add only a small number of items/weapons from the Open Adventure rulebook to show how it's done.
|
||||
- We will add only a small number of items/weapons from the Open Adventure rulebook to show how it's done.
|
||||
|
||||
#### What are the game mechanics? How do you decide if an action succeeds or fails?
|
||||
### What are the game mechanics? How do you decide if an action succeeds or fails?
|
||||
|
||||
Open Adventure's conflict resolution is based on adding a trait (such as Strength) with a random number in
|
||||
order to beat a target. We will emulate this in code.
|
||||
Open Adventure's conflict resolution is based on adding a trait (such as Strength) with a random number in
|
||||
order to beat a target. We will emulate this in code.
|
||||
|
||||
Having a "skill" means getting a bonus to that roll for a more narrow action.
|
||||
Having a "skill" means getting a bonus to that roll for a more narrow action.
|
||||
Since the computer will need to know exactly what those skills are, we will add them more explicitly than
|
||||
in the rules, but we will only add the minimum to show off the functionality we need.
|
||||
|
||||
#### Does the flow of time matter in your game - does night and day change? What about seasons?
|
||||
### Does the flow of time matter in your game - does night and day change? What about seasons?
|
||||
|
||||
Most commonly, game-time runs faster than real-world time. There are
|
||||
a few advantages with this:
|
||||
Most commonly, game-time runs faster than real-world time. There are
|
||||
a few advantages with this:
|
||||
|
||||
- Unlike in a single-player game, you can't fast-forward time in a multiplayer game if you are waiting for
|
||||
something, like NPC shops opening.
|
||||
- Unlike in a single-player game, you can't fast-forward time in a multiplayer game if you are waiting for
|
||||
something, like NPC shops opening.
|
||||
- Healing and other things that we know takes time will go faster while still being reasonably 'realistic'.
|
||||
|
||||
The main drawback is for games with slower roleplay pace. While you are having a thoughtful roleplaying scene
|
||||
over dinner, the game world reports that two days have passed. Having a slower game time than real-time is
|
||||
a less common, but possible solution for such games.
|
||||
over dinner, the game world reports that two days have passed. Having a slower game time than real-time is
|
||||
a less common, but possible solution for such games.
|
||||
|
||||
It is however _not_ recommended to let game-time exactly equal the speed of real time. The reason for this
|
||||
is that people will join your game from all around the world, and they will often only be able to play at
|
||||
particular times of their day. With a game-time drifting relative real-time, everyone will eventually be
|
||||
able to experience both day and night in the game.
|
||||
It is however _not_ recommended to let game-time exactly equal the speed of real time. The reason for this
|
||||
is that people will join your game from all around the world, and they will often only be able to play at
|
||||
particular times of their day. With a game-time drifting relative real-time, everyone will eventually be
|
||||
able to experience both day and night in the game.
|
||||
|
||||
For this tutorial-game we will go with Evennia's default, which is that the game-time runs two times faster
|
||||
than real time.
|
||||
|
||||
#### Do you want changing, global weather or should weather just be set manually in roleplay?
|
||||
### Do you want changing, global weather or should weather just be set manually in roleplay?
|
||||
|
||||
A weather system is a good example of a game-global system that affects a subset of game entities
|
||||
(outdoor rooms). We will not be doing any advanced weather simulation, but we'll show how to do
|
||||
random weather changes happening across the game world.
|
||||
A weather system is a good example of a game-global system that affects a subset of game entities
|
||||
(outdoor rooms). We will not be doing any advanced weather simulation, but we'll show how to do
|
||||
random weather changes happening across the game world.
|
||||
|
||||
#### Do you want a coded world-economy or just a simple barter system? Or no formal economy at all?
|
||||
### Do you want a coded world-economy or just a simple barter system? Or no formal economy at all?
|
||||
|
||||
We will allow for money and barter/trade between NPCs/Players and Player/Player, but will not care about
|
||||
inflation. A real economic simulation could do things like modify shop prices based on supply and demand.
|
||||
We will allow for money and barter/trade between NPCs/Players and Player/Player, but will not care about
|
||||
inflation. A real economic simulation could do things like modify shop prices based on supply and demand.
|
||||
We will not go down that rabbit hole.
|
||||
|
||||
#### Do you have concepts like reputation and influence?
|
||||
|
||||
These are useful things for a more social-interaction heavy game. We will not include them for this
|
||||
tutorial however.
|
||||
### Do you have concepts like reputation and influence?
|
||||
|
||||
#### Will your characters be known by their name or only by their physical appearance?
|
||||
These are useful things for a more social-interaction heavy game. We will not include them for this
|
||||
tutorial however.
|
||||
|
||||
### Will your characters be known by their name or only by their physical appearance?
|
||||
|
||||
This is a common thing in RP-heavy games. Others will only see you as "The tall woman" until you
|
||||
introduce yourself and they 'recognize' you with a name. Linked to this is the concept of more complex
|
||||
emoting and posing.
|
||||
emoting and posing.
|
||||
|
||||
Adding such a system from scratch is complex and way beyond the scope of this tutorial. However,
|
||||
there is an existing Evennia contrib that adds all of this functionality and more, so we will
|
||||
Adding such a system from scratch is complex and way beyond the scope of this tutorial. However,
|
||||
there is an existing Evennia contrib that adds all of this functionality and more, so we will
|
||||
include that and explain briefly how it works.
|
||||
|
||||
### Rooms
|
||||
## Rooms
|
||||
|
||||
#### Is a simple room description enough or should the description be able to change?
|
||||
### Is a simple room description enough or should the description be able to change?
|
||||
|
||||
Changing room descriptions for day and night, winder and summer is actually quite easy to do, but looks
|
||||
very impressive. We happen to know there is also a contrib that helps with this, so we'll show how to
|
||||
include that.
|
||||
Changing room descriptions for day and night, winder and summer is actually quite easy to do, but looks
|
||||
very impressive. We happen to know there is also a contrib that helps with this, so we'll show how to
|
||||
include that.
|
||||
|
||||
#### Should the room have different statuses?
|
||||
### Should the room have different statuses?
|
||||
|
||||
We will have different weather in outdoor rooms, but this will not have any gameplay effect - bow strings
|
||||
will not get wet and fireballs will not fizzle if it rains.
|
||||
will not get wet and fireballs will not fizzle if it rains.
|
||||
|
||||
#### Can objects be hidden in the room? Can a person hide in the room?
|
||||
### Can objects be hidden in the room? Can a person hide in the room?
|
||||
|
||||
We will not model hiding and stealth. This will be a game of honorable face-to-face conflict.
|
||||
|
||||
### Objects
|
||||
## Objects
|
||||
|
||||
#### How numerous are your objects? Do you want large loot-lists or are objects just role playing props?
|
||||
### How numerous are your objects? Do you want large loot-lists or are objects just role playing props?
|
||||
|
||||
Since we are not going for a pure freeform RPG here, we will want objects with properties, like weapons
|
||||
and potions and such. Monsters should drop loot even though our list of objects will not be huge.
|
||||
Since we are not going for a pure freeform RPG here, we will want objects with properties, like weapons
|
||||
and potions and such. Monsters should drop loot even though our list of objects will not be huge.
|
||||
|
||||
#### Is each coin a separate object or do you just store a bank account value?
|
||||
### Is each coin a separate object or do you just store a bank account value?
|
||||
|
||||
Since we will use bartering, placing coin objects on one side of the barter makes for a simple way to
|
||||
Since we will use bartering, placing coin objects on one side of the barter makes for a simple way to
|
||||
handle payments. So we will use coins as-objects.
|
||||
|
||||
#### Do multiple similar objects form stacks and how are those stacks handled in that case?
|
||||
### Do multiple similar objects form stacks and how are those stacks handled in that case?
|
||||
|
||||
Since we'll use coins, it's practical to have them and other items stack together. While Evennia does not
|
||||
do this natively, we will make use of a contrib for this.
|
||||
Since we'll use coins, it's practical to have them and other items stack together. While Evennia does not
|
||||
do this natively, we will make use of a contrib for this.
|
||||
|
||||
#### Does an object have weight or volume (so you cannot carry an infinite amount of them)?
|
||||
### Does an object have weight or volume (so you cannot carry an infinite amount of them)?
|
||||
|
||||
Limiting carrying weight is one way to stop players from hoarding. It also makes it more important
|
||||
for players to pick only the equipment they need. Carrying limits can easily come across as
|
||||
annoying to players though, so one needs to be careful with it.
|
||||
Limiting carrying weight is one way to stop players from hoarding. It also makes it more important
|
||||
for players to pick only the equipment they need. Carrying limits can easily come across as
|
||||
annoying to players though, so one needs to be careful with it.
|
||||
|
||||
Open Adventure rules include weight limits, so we will include them.
|
||||
Open Adventure rules include weight limits, so we will include them.
|
||||
|
||||
#### Can objects be broken? Can they be repaired?
|
||||
### Can objects be broken? Can they be repaired?
|
||||
|
||||
Item breakage is very useful for a game economy; breaking weapons adds tactical considerations (if it's not
|
||||
too common, then it becomes annoying) and repairing things gives work for crafting players.
|
||||
Item breakage is very useful for a game economy; breaking weapons adds tactical considerations (if it's not
|
||||
too common, then it becomes annoying) and repairing things gives work for crafting players.
|
||||
|
||||
We wanted a crafting system, so this is what we will limit it to - repairing items using some sort
|
||||
of raw materials.
|
||||
We wanted a crafting system, so this is what we will limit it to - repairing items using some sort
|
||||
of raw materials.
|
||||
|
||||
#### Can you fight with a chair or a flower or must you use a special 'weapon' kind of thing?
|
||||
### Can you fight with a chair or a flower or must you use a special 'weapon' kind of thing?
|
||||
|
||||
Traditionally, only 'weapons' could be used to fight with. In the past this was a useful
|
||||
simplification, but with Python classes and inheritance, it's not actually more work to just
|
||||
let all items in game work as a weapon in a pinch.
|
||||
Traditionally, only 'weapons' could be used to fight with. In the past this was a useful
|
||||
simplification, but with Python classes and inheritance, it's not actually more work to just
|
||||
let all items in game work as a weapon in a pinch.
|
||||
|
||||
So for our game we will let a character use any item they want as a weapon. The difference will
|
||||
be that non-weapon items will do less damage and also break and become unusable much quicker.
|
||||
So for our game we will let a character use any item they want as a weapon. The difference will
|
||||
be that non-weapon items will do less damage and also break and become unusable much quicker.
|
||||
|
||||
#### Will characters be able to craft new objects?
|
||||
### Will characters be able to craft new objects?
|
||||
|
||||
Crafting is a common feature in multiplayer games. In code it usually means using a skill-check
|
||||
to combine base ingredients from a fixed recipe in order to create a new item. The classic
|
||||
example is to combine _leather straps_, a _hilt_, a _pommel_ and a _blade_ to make a new _sword_.
|
||||
A full-fledged crafting system could require multiple levels of crafting, including having to mine
|
||||
for ore or cut down trees for wood.
|
||||
Crafting is a common feature in multiplayer games. In code it usually means using a skill-check
|
||||
to combine base ingredients from a fixed recipe in order to create a new item. The classic
|
||||
example is to combine _leather straps_, a _hilt_, a _pommel_ and a _blade_ to make a new _sword_.
|
||||
A full-fledged crafting system could require multiple levels of crafting, including having to mine
|
||||
for ore or cut down trees for wood.
|
||||
|
||||
In our case we will limit our crafting to repairing broken items. To show how it's done, we will require
|
||||
extra items (a recipe) in order to facilitate the repairs.
|
||||
extra items (a recipe) in order to facilitate the repairs.
|
||||
|
||||
#### Should mobs/NPCs have some sort of AI?
|
||||
### Should mobs/NPCs have some sort of AI?
|
||||
|
||||
A rule of adding Artificial Intelligence is that with today's technology you should not hope to fool
|
||||
A rule of adding Artificial Intelligence is that with today's technology you should not hope to fool
|
||||
anyone with it anytime soon. Unless you have a side-gig as an AI researcher, users will likely
|
||||
not notice any practical difference between a simple state-machine and you spending a lot of time learning
|
||||
how to train a neural net.
|
||||
how to train a neural net.
|
||||
|
||||
For this tutorial, we will show how to add a simple state-machine for monsters. NPCs will only be
|
||||
shop-keepers and quest-gives so they won't need any real AI to speak of.
|
||||
For this tutorial, we will show how to add a simple state-machine for monsters. NPCs will only be
|
||||
shop-keepers and quest-gives so they won't need any real AI to speak of.
|
||||
|
||||
#### Are NPCs and mobs different entities? How do they differ?
|
||||
### Are NPCs and mobs different entities? How do they differ?
|
||||
|
||||
"Mobs" or "mobiles" are things that move around. This is traditionally monsters you can fight with, but could
|
||||
"Mobs" or "mobiles" are things that move around. This is traditionally monsters you can fight with, but could
|
||||
also be city guards or the baker going to chat with the neighbor. Back in the day, they were often fundamentally
|
||||
different these days it's often easier to just make NPCs and mobs essentially the same thing.
|
||||
different these days it's often easier to just make NPCs and mobs essentially the same thing.
|
||||
|
||||
In EvAdventure, both Monsters and NPCs will be the same type of thing; A monster could give you a quest
|
||||
and an NPC might fight you as a mob as well as trade with you.
|
||||
and an NPC might fight you as a mob as well as trade with you.
|
||||
|
||||
#### _Should there be NPCs giving quests? If so, how do you track Quest status?
|
||||
### _Should there be NPCs giving quests? If so, how do you track Quest status?
|
||||
|
||||
We will design a simple quest system to track the status of ongoing quests.
|
||||
We will design a simple quest system to track the status of ongoing quests.
|
||||
|
||||
### Characters
|
||||
## Characters
|
||||
|
||||
#### Can players have more than one Character active at a time or are they allowed to multi-play?
|
||||
### Can players have more than one Character active at a time or are they allowed to multi-play?
|
||||
|
||||
Since Evennia differentiates between `Sessions` (the client-connection to the game), `Accounts`
|
||||
and `Character`s, it natively supports multi-play. This is controlled by the `MULTISESSION_MODE`
|
||||
setting, which has a value from `0` (default) to `3`.
|
||||
Since Evennia differentiates between `Sessions` (the client-connection to the game), `Accounts`
|
||||
and `Character`s, it natively supports multi-play. This is controlled by the `MULTISESSION_MODE`
|
||||
setting, which has a value from `0` (default) to `3`.
|
||||
|
||||
- `0`- One Character per Account and one Session per Account. This means that if you login to the same
|
||||
- `0`- One Character per Account and one Session per Account. This means that if you login to the same
|
||||
account from another client you'll be disconnected from the first. When creating a new account, a Character
|
||||
will be auto-created with the same name as your Account. This is default mode and mimics legacy code bases
|
||||
which had no separation between Account and Character.
|
||||
- `1` - One Character per Account, multiple Sessions per Account. So you can connect simultaneously from
|
||||
which had no separation between Account and Character.
|
||||
- `1` - One Character per Account, multiple Sessions per Account. So you can connect simultaneously from
|
||||
multiple clients and see the same output in all of them.
|
||||
- `2` - Multiple Characters per Account, one Session per Character. This will not auto-create a same-named
|
||||
Character for you, instead you get to create/choose between a number of Characters up to a max limit given by
|
||||
- `2` - Multiple Characters per Account, one Session per Character. This will not auto-create a same-named
|
||||
Character for you, instead you get to create/choose between a number of Characters up to a max limit given by
|
||||
the `MAX_NR_CHARACTERS` setting (default 1). You can play them all simultaneously if you have multiple clients
|
||||
open, but only one client per Character.
|
||||
- `3` - Multiple Characters per Account, Multiple Sessions per Character. This is like mode 2, except players
|
||||
can control each Character from multiple clients, seeing the same output from each Character.
|
||||
can control each Character from multiple clients, seeing the same output from each Character.
|
||||
|
||||
We will go with a multi-role game, so we will use `MULTISESSION_MODE=3` for this tutorial.
|
||||
We will go with a multi-role game, so we will use `MULTISESSION_MODE=3` for this tutorial.
|
||||
|
||||
#### How does the character-generation work?
|
||||
### How does the character-generation work?
|
||||
|
||||
There are a few common ways to do character generation:
|
||||
|
||||
- Rooms. This is the traditional way. Each room's description tells you what command to use to modify
|
||||
- Rooms. This is the traditional way. Each room's description tells you what command to use to modify
|
||||
your character. When you are done you move to the next room. Only use this if you have another reason for
|
||||
using a room, like having a training dummy to test skills on, for example.
|
||||
- A Menu. The Evennia _EvMenu_ system allows you to code very flexible in-game menus without needing to walk
|
||||
- A Menu. The Evennia _EvMenu_ system allows you to code very flexible in-game menus without needing to walk
|
||||
between rooms. You can both have a step-by-step menu (a 'wizard') or allow the user to jump between the
|
||||
steps as they please. This tends to be a lot easier for newcomers to understand since it doesn't require
|
||||
steps as they please. This tends to be a lot easier for newcomers to understand since it doesn't require
|
||||
using custom commands they will likely never use again after this.
|
||||
- Questions. A fun way to build a character is to answer a series of questions. This is usually implemented
|
||||
with a sequential menu.
|
||||
with a sequential menu.
|
||||
|
||||
For the tutorial we will use a menu to let the user modify each section of their character sheet in any order
|
||||
until they are happy.
|
||||
|
||||
#### How do you implement different "classes" or "races"?
|
||||
until they are happy.
|
||||
|
||||
The way classes and races work in most RPGs (as well as in OpenAdventure) is that they act as static 'templates'
|
||||
that inform which bonuses and special abilities you have. This means that all we need to store on the
|
||||
Character is _which_ class and _which_ race they have; the actual logic can sit in Python code and just
|
||||
be looked up when we need it.
|
||||
### How do you implement different "classes" or "races"?
|
||||
|
||||
#### If a Character can hide in a room, what skill will decide if they are detected?
|
||||
The way classes and races work in most RPGs (as well as in OpenAdventure) is that they act as static 'templates'
|
||||
that inform which bonuses and special abilities you have. This means that all we need to store on the
|
||||
Character is _which_ class and _which_ race they have; the actual logic can sit in Python code and just
|
||||
be looked up when we need it.
|
||||
|
||||
Hiding means a few things.
|
||||
### If a Character can hide in a room, what skill will decide if they are detected?
|
||||
|
||||
Hiding means a few things.
|
||||
- The Character should not appear in the room's description / character list
|
||||
- Others hould not be able to interact with a hidden character. It'd be weird if you could do `attack <name>`
|
||||
or `look <name>` if the named character is in hiding.
|
||||
- There must be a way for the person to come out of hiding, and probably for others to search or accidentally
|
||||
find the person (probably based on skill checks).
|
||||
- The room will also need to be involved, maybe with some modifier as to how easy it is to hide in the room.
|
||||
- The room will also need to be involved, maybe with some modifier as to how easy it is to hide in the room.
|
||||
|
||||
We will _not_ be including a hide-mechanic in EvAdventure though.
|
||||
We will _not_ be including a hide-mechanic in EvAdventure though.
|
||||
|
||||
#### What does the skill tree look like? Can a Character gain experience to improve? By killing enemies? Solving quests? By roleplaying?
|
||||
### What does the skill tree look like? Can a Character gain experience to improve? By killing enemies? Solving quests? By roleplaying?
|
||||
|
||||
Gaining experience points (XP) and improving one's character is a staple of roleplaying games. There are many
|
||||
ways to implement this:
|
||||
- Gaining XP from kills is very common; it's easy to let a monster be 'worth' a certain number of XP and it's
|
||||
easy to tell when you should gain it.
|
||||
Gaining experience points (XP) and improving one's character is a staple of roleplaying games. There are many
|
||||
ways to implement this:
|
||||
- Gaining XP from kills is very common; it's easy to let a monster be 'worth' a certain number of XP and it's
|
||||
easy to tell when you should gain it.
|
||||
- Gaining XP from quests is the same - each quest is 'worth' XP and you get them when completing the test.
|
||||
- Gaining XP from roleplay is harder to define. Different games have tried a lot of different ways to do this:
|
||||
- XP from being online - just being online gains you XP. This inflates player numbers but many players may
|
||||
- XP from being online - just being online gains you XP. This inflates player numbers but many players may
|
||||
just be lurking and not be actually playing the game at any given time.
|
||||
- XP from roleplaying scenes - you gain XP according to some algorithm analyzing your emotes for 'quality',
|
||||
how often you post, how long your emotes are etc.
|
||||
- XP from roleplaying scenes - you gain XP according to some algorithm analyzing your emotes for 'quality',
|
||||
how often you post, how long your emotes are etc.
|
||||
- XP from actions - you gain XP when doing things, anything. Maybe your XP is even specific to each action, so
|
||||
you gain XP only for running when you run, XP for your axe skill when you fight with an axe etc.
|
||||
- XP from fails - you only gain XP when failing rolls.
|
||||
- XP from other players - other players can award you XP for good RP.
|
||||
|
||||
For EvAdventure we will use Open Adventure's rules for XP, which will be driven by kills and quest successes.
|
||||
|
||||
#### May player-characters attack each other (PvP)?
|
||||
- XP from fails - you only gain XP when failing rolls.
|
||||
- XP from other players - other players can award you XP for good RP.
|
||||
|
||||
Deciding this affects the style of your entire game. PvP makes for exciting gameplay but it opens a whole new
|
||||
can of worms when it comes to "fairness". Players will usually accept dying to an overpowered NPC dragon. They
|
||||
will not be as accepting if they perceive another player is perceived as being overpowered. PvP means that you
|
||||
have to be very careful to balance the game - all characters does not have to be exactly equal but they should
|
||||
all be viable to play a fun game with. PvP does not only mean combat though. Players can compete in all sorts of ways, including gaining influence in
|
||||
a political game or gaining market share when selling their crafted merchandise.
|
||||
For EvAdventure we will use Open Adventure's rules for XP, which will be driven by kills and quest successes.
|
||||
|
||||
### May player-characters attack each other (PvP)?
|
||||
|
||||
Deciding this affects the style of your entire game. PvP makes for exciting gameplay but it opens a whole new
|
||||
can of worms when it comes to "fairness". Players will usually accept dying to an overpowered NPC dragon. They
|
||||
will not be as accepting if they perceive another player is perceived as being overpowered. PvP means that you
|
||||
have to be very careful to balance the game - all characters does not have to be exactly equal but they should
|
||||
all be viable to play a fun game with. PvP does not only mean combat though. Players can compete in all sorts of ways, including gaining influence in
|
||||
a political game or gaining market share when selling their crafted merchandise.
|
||||
|
||||
For the EvAdventure we will support both Player-vs-environment combat and turn-based PvP. We will allow players
|
||||
to barter with each other (so potentially scam others?) but that's the extent of it. We will focus on showing
|
||||
off techniques and will not focus on making a balanced game.
|
||||
off techniques and will not focus on making a balanced game.
|
||||
|
||||
#### What are the penalties of defeat? Permanent death? Quick respawn? Time in prison?
|
||||
### What are the penalties of defeat? Permanent death? Quick respawn? Time in prison?
|
||||
|
||||
This is another big decision that strongly affects the mood and style of your game.
|
||||
|
||||
Perma-death means that once your character dies, it's gone and you have to make a new one.
|
||||
Perma-death means that once your character dies, it's gone and you have to make a new one.
|
||||
|
||||
- It allows for true heroism. If you genuinely risk losing your character of two years to fight the dragon,
|
||||
- It allows for true heroism. If you genuinely risk losing your character of two years to fight the dragon,
|
||||
your triumph is an actual feat.
|
||||
- It limits the old-timer dominance problem. If long-time players dies occationally, it will open things
|
||||
up for newcomers.
|
||||
- It lowers inflation, since the hoarded resources of a dead character can be removed.
|
||||
- It gives capital punishment genuine discouraging power.
|
||||
- It's realistic.
|
||||
up for newcomers.
|
||||
- It lowers inflation, since the hoarded resources of a dead character can be removed.
|
||||
- It gives capital punishment genuine discouraging power.
|
||||
- It's realistic.
|
||||
|
||||
Perma-death comes with some severe disadvantages however.
|
||||
Perma-death comes with some severe disadvantages however.
|
||||
|
||||
- It's impopular. Many players will just not play a game where they risk losing their beloved character
|
||||
just like that.
|
||||
- It's impopular. Many players will just not play a game where they risk losing their beloved character
|
||||
just like that.
|
||||
- Many players say they like the _idea_ of permadeath except when it could happen to them.
|
||||
- It can limit roleplaying freedom and make people refuse to take any risks.
|
||||
- It may make players even more reluctant to play conflict-driving 'bad guys'.
|
||||
- Game balance is much, much more important when results are "final". This escalates the severity of 'unfairness'
|
||||
a hundred-fold. Things like bugs or exploits can also lead to much more server effects.
|
||||
|
||||
|
||||
For these reasons, it's very common to do hybrid systems. Some tried variations:
|
||||
|
||||
- NPCs cannot kill you, only other players can.
|
||||
- NPCs cannot kill you, only other players can.
|
||||
- Death is permanent, but it's difficult to actually die - you are much more likely to end up being severely
|
||||
hurt/incapacitated.
|
||||
hurt/incapacitated.
|
||||
- You can pre-pay 'insurance' to magically/technologically avoid actually dying. Only if don't have insurance will
|
||||
you die permanently.
|
||||
- Death just means harsh penalties, not actual death.
|
||||
- When you die you can fight your way back to life from some sort of afterlife.
|
||||
- When you die you can fight your way back to life from some sort of afterlife.
|
||||
- You'll only die permanently if you as a player explicitly allows it.
|
||||
|
||||
|
||||
For our tutorial-game we will not be messing with perma-death; instead your defeat will mean you will re-spawn
|
||||
back at your home location with a fraction of your health.
|
||||
back at your home location with a fraction of your health.
|
||||
|
||||
## Conclusions
|
||||
## Conclusions
|
||||
|
||||
Going through the questions has helped us get a little bit more of a feel for the game we want to do. There are
|
||||
Going through the questions has helped us get a little bit more of a feel for the game we want to do. There are
|
||||
many other things we could ask ourselves, but if we can cover these points we will be a good way towards a complete,
|
||||
playable game!
|
||||
playable game!
|
||||
|
||||
Before starting to code in earnest a good coder should always do an inventory of all the stuff they _don't_ need
|
||||
to code themselves. So in the next lesson we will check out what help we have from Evennia's _contribs_.
|
||||
to code themselves. So in the next lesson we will check out what help we have from Evennia's _contribs_.
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ question. And maybe you’ll find that you have a better feeling for the answer
|
|||
|
||||
Once you are out of the starting tutorial, you'll be off to do your own thing.
|
||||
|
||||
- The starting tutorial cannot cover everything. Skim through the [Evennia docs](../../../index).
|
||||
- The starting tutorial cannot cover everything. Skim through the [Evennia docs](../../../index.md).
|
||||
Even if you don't read everything, it gives you a feeling for what's available should you need
|
||||
to look for something later. Make sure to use the search function.
|
||||
- You can now start by expanding on the tutorial-game we will have created. In the last part there
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
# Evennia Starting Tutorial (Part 2)
|
||||
|
||||
```sidebar:: Tutorial Parts
|
||||
```{eval-rst}
|
||||
.. sidebar:: Tutorial Parts
|
||||
|
||||
Part 1: `What we have <../Part1/Starting-Part1.html>`_
|
||||
A tour of Evennia and how to use the tools, including an introduction to Python.
|
||||
**Part 2: What we want**
|
||||
Planning our tutorial game and what to think about when planning your own in the future.
|
||||
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
|
||||
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
|
||||
Getting down to the meat of extending Evennia to make our game
|
||||
Part 4: `Using what we created <../Part4/Starting-Part4.html>`_
|
||||
Building a tech-demo and world content to go with our code
|
||||
|
|
@ -16,25 +17,25 @@
|
|||
|
||||
## Lessons for Part 2
|
||||
|
||||
In Part two of the Starting tutorial we'll step back and plan out the kind of tutorial
|
||||
In Part two of the Starting tutorial we'll step back and plan out the kind of tutorial
|
||||
game we want to make. This is a more 'theoretical' part where we won't do any hands-on
|
||||
programming.
|
||||
programming.
|
||||
|
||||
1. Introduction & Overview (you are here)
|
||||
1. [Where do I begin](./Planning-Where-Do-I-Begin)
|
||||
1. [On planning a game](./Game-Planning)
|
||||
1. [Planning to use some useful Contribs](./Planning-Some-Useful-Contribs)
|
||||
1. [Where do I begin](./Planning-Where-Do-I-Begin.md)
|
||||
1. [On planning a game](./Game-Planning.md)
|
||||
1. [Planning to use some useful Contribs](./Planning-Some-Useful-Contribs.md)
|
||||
|
||||
In the process we'll go through the common questions of "where to start"
|
||||
and "what to think about" when creating a multiplayer online text game.
|
||||
|
||||
|
||||
```toctree::
|
||||
:hidden:
|
||||
```{toctree}
|
||||
:hidden:
|
||||
|
||||
Planning-Where-Do-I-Begin
|
||||
Game-Planning
|
||||
Planning-Some-Useful-Contribs
|
||||
../Part3/Starting-Part3
|
||||
Planning-Where-Do-I-Begin
|
||||
Game-Planning
|
||||
Planning-Some-Useful-Contribs
|
||||
../Part3/Starting-Part3
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
[prev lesson](../../../Unimplemented) | [next lesson](../../../Unimplemented)
|
||||
[prev lesson](../../../Unimplemented.md) | [next lesson](../../../Unimplemented.md)
|
||||
|
||||
# Making a sittable object
|
||||
# Making a sittable object
|
||||
|
||||
In this lesson we will go through how to make a chair you can sit on. Sounds easy, right?
|
||||
Well it is. But in the process of making the chair we will need to consider the various ways
|
||||
to do it depending on how we want our game to work.
|
||||
In this lesson we will go through how to make a chair you can sit on. Sounds easy, right?
|
||||
Well it is. But in the process of making the chair we will need to consider the various ways
|
||||
to do it depending on how we want our game to work.
|
||||
|
||||
The goals of this lesson are as follows:
|
||||
|
||||
|
||||
- We want a new 'sittable' object, a Chair in particular".
|
||||
- We want to be able to use a command to sit in the chair.
|
||||
- We want to be able to use a command to sit in the chair.
|
||||
- Once we are sitting in the chair it should affect us somehow. To demonstrate this we'll
|
||||
set a flag "Resting" on the Character sitting in the Chair.
|
||||
set a flag "Resting" on the Character sitting in the Chair.
|
||||
- When you sit down you should not be able to walk to another room without first standing up.
|
||||
- A character should be able to stand up and move away from the chair.
|
||||
- A character should be able to stand up and move away from the chair.
|
||||
|
||||
There are two main ways to design the commands for sitting and standing up.
|
||||
- You can store the commands on the chair so they are only available when a chair is in the room
|
||||
- You can store the commands on the Character so they are always available and you must always specify
|
||||
which chair to sit on.
|
||||
|
||||
|
||||
Both of these are very useful to know about, so in this lesson we'll try both. But first
|
||||
we need to handle some basics.
|
||||
we need to handle some basics.
|
||||
|
||||
|
||||
## Don't move us when resting
|
||||
|
||||
When you are sitting in a chair you can't just walk off without first standing up.
|
||||
When you are sitting in a chair you can't just walk off without first standing up.
|
||||
This requires a change to our Character typeclass. Open `mygame/typeclasses/characters.py`:
|
||||
|
||||
```python
|
||||
|
|
@ -39,7 +39,7 @@ class Character(DefaultCharacter):
|
|||
def at_before_move(self, destination):
|
||||
"""
|
||||
Called by self.move_to when trying to move somewhere. If this returns
|
||||
False, the move is immediately cancelled.
|
||||
False, the move is immediately cancelled.
|
||||
"""
|
||||
if self.db.is_resting:
|
||||
self.msg("You can't go anywhere while resting.")
|
||||
|
|
@ -48,15 +48,15 @@ class Character(DefaultCharacter):
|
|||
|
||||
```
|
||||
|
||||
When moving somewhere, [character.move_to](api.objects.objects#Object.move_to) is called. This in turn
|
||||
When moving somewhere, [character.move_to](evennia.objects.objects.DefaultObject.move_to) is called. This in turn
|
||||
will call `character.at_before_move`. Here we look for an Attribute `is_resting` (which we will assign below)
|
||||
to determine if we are stuck on the chair or not.
|
||||
to determine if we are stuck on the chair or not.
|
||||
|
||||
## Making the Chair itself
|
||||
## Making the Chair itself
|
||||
|
||||
Next we need the Chair itself, or rather a whole family of "things you can sit on" that we will call
|
||||
_sittables_. We can't just use a default Object since we want a sittable to contain some custom code. We need
|
||||
a new, custom Typeclass. Create a new module `mygame/typeclasses/sittables.py` with the following content:
|
||||
Next we need the Chair itself, or rather a whole family of "things you can sit on" that we will call
|
||||
_sittables_. We can't just use a default Object since we want a sittable to contain some custom code. We need
|
||||
a new, custom Typeclass. Create a new module `mygame/typeclasses/sittables.py` with the following content:
|
||||
|
||||
```python
|
||||
|
||||
|
|
@ -69,8 +69,8 @@ class Sittable(DefaultObject):
|
|||
|
||||
def do_sit(self, sitter):
|
||||
"""
|
||||
Called when trying to sit on/in this object.
|
||||
|
||||
Called when trying to sit on/in this object.
|
||||
|
||||
Args:
|
||||
sitter (Object): The one trying to sit down.
|
||||
|
||||
|
|
@ -78,43 +78,43 @@ class Sittable(DefaultObject):
|
|||
current = self.db.sitter
|
||||
if current:
|
||||
if current == sitter:
|
||||
sitter.msg("You are already sitting on {self.key}.")
|
||||
sitter.msg("You are already sitting on {self.key}.")
|
||||
else:
|
||||
sitter.msg(f"You can't sit on {self.key} "
|
||||
f"- {current.key} is already sitting there!")
|
||||
return
|
||||
return
|
||||
self.db.sitting = sitter
|
||||
sitter.db.is_resting = True
|
||||
sitter.msg(f"You sit on {self.key}")
|
||||
|
||||
def do_stand(self, stander):
|
||||
"""
|
||||
Called when trying to stand from this object.
|
||||
Called when trying to stand from this object.
|
||||
|
||||
Args:
|
||||
stander (Object): The one trying to stand up.
|
||||
|
||||
"""
|
||||
current = self.db.sitter
|
||||
current = self.db.sitter
|
||||
if not stander == current:
|
||||
stander.msg(f"You are not sitting on {self.key}.")
|
||||
else:
|
||||
self.db.sitting = None
|
||||
stander.db.is_resting = False
|
||||
stander.db.is_resting = False
|
||||
stander.msg(f"You stand up from {self.key}")
|
||||
```
|
||||
|
||||
Here we have a small Typeclass that handles someone trying to sit on it. It has two methods that we can simply
|
||||
Here we have a small Typeclass that handles someone trying to sit on it. It has two methods that we can simply
|
||||
call from a Command later. We set the `is_resting` Attribute on the one sitting down.
|
||||
|
||||
One could imagine that one could have the future `sit` command check if someone is already sitting in the
|
||||
|
||||
One could imagine that one could have the future `sit` command check if someone is already sitting in the
|
||||
chair instead. This would work too, but letting the `Sittable` class handle the logic around who can sit on it makes
|
||||
logical sense.
|
||||
logical sense.
|
||||
|
||||
We let the typeclass handle the logic, and also let it do all the return messaging. This makes it easy to churn out
|
||||
a bunch of chairs for people to sit on. But it's not perfect. The `Sittable` class is general. What if you want to
|
||||
make an armchair. You sit "in" an armchair rather than "on" it. We _could_ make a child class of `Sittable` named
|
||||
`SittableIn` that makes this change, but that feels excessive. Instead we will make it so that Sittables can
|
||||
`SittableIn` that makes this change, but that feels excessive. Instead we will make it so that Sittables can
|
||||
modify this per-instance:
|
||||
|
||||
|
||||
|
|
@ -126,13 +126,13 @@ class Sittable(DefaultObject):
|
|||
|
||||
def at_object_creation(self):
|
||||
self.db.sitter = None
|
||||
# do you sit "on" or "in" this object?
|
||||
# do you sit "on" or "in" this object?
|
||||
self.db.adjective = "on"
|
||||
|
||||
def do_sit(self, sitter):
|
||||
"""
|
||||
Called when trying to sit on/in this object.
|
||||
|
||||
Called when trying to sit on/in this object.
|
||||
|
||||
Args:
|
||||
sitter (Object): The one trying to sit down.
|
||||
|
||||
|
|
@ -141,46 +141,46 @@ class Sittable(DefaultObject):
|
|||
current = self.db.sitter
|
||||
if current:
|
||||
if current == sitter:
|
||||
sitter.msg(f"You are already sitting {adjective} {self.key}.")
|
||||
sitter.msg(f"You are already sitting {adjective} {self.key}.")
|
||||
else:
|
||||
sitter.msg(
|
||||
f"You can't sit {adjective} {self.key} "
|
||||
f"- {current.key} is already sitting there!")
|
||||
return
|
||||
return
|
||||
self.db.sitting = sitter
|
||||
sitter.db.is_resting = True
|
||||
sitter.msg(f"You sit {adjective} {self.key}")
|
||||
|
||||
def do_stand(self, stander):
|
||||
"""
|
||||
Called when trying to stand from this object.
|
||||
Called when trying to stand from this object.
|
||||
|
||||
Args:
|
||||
stander (Object): The one trying to stand up.
|
||||
|
||||
"""
|
||||
current = self.db.sitter
|
||||
current = self.db.sitter
|
||||
if not stander == current:
|
||||
stander.msg(f"You are not sitting {self.db.adjective} {self.key}.")
|
||||
else:
|
||||
self.db.sitting = None
|
||||
stander.db.is_resting = False
|
||||
stander.db.is_resting = False
|
||||
stander.msg(f"You stand up from {self.key}")
|
||||
```
|
||||
|
||||
We added a new Attribute `adjective` which will probably usually be `in` or `on` but could also be `at` if you
|
||||
want to be able to sit _at a desk_ for example. A regular builder would use it like this:
|
||||
want to be able to sit _at a desk_ for example. A regular builder would use it like this:
|
||||
|
||||
> create/drop armchair : sittables.Sittable
|
||||
> set armchair/adjective = in
|
||||
|
||||
This is probably enough. But all those strings are hard-coded. What if we want some more dramatic flair when you
|
||||
sit down?
|
||||
sit down?
|
||||
|
||||
You sit down and a whoopie cushion makes a loud fart noise!
|
||||
You sit down and a whoopie cushion makes a loud fart noise!
|
||||
|
||||
For this we need to allow some further customization. Let's let the current strings be defaults that
|
||||
we can replace.
|
||||
For this we need to allow some further customization. Let's let the current strings be defaults that
|
||||
we can replace.
|
||||
|
||||
```python
|
||||
|
||||
|
|
@ -188,13 +188,13 @@ from evennia import DefaultObject
|
|||
|
||||
class Sittable(DefaultObject):
|
||||
"""
|
||||
An object one can sit on
|
||||
An object one can sit on
|
||||
|
||||
Customizable Attributes:
|
||||
Customizable Attributes:
|
||||
adjective: How to sit (on, in, at etc)
|
||||
Return messages (set as Attributes):
|
||||
msg_already_sitting: Already sitting here
|
||||
format tokens {adjective} and {key}
|
||||
format tokens {adjective} and {key}
|
||||
msg_other_sitting: Someone else is sitting here.
|
||||
format tokens {adjective}, {key} and {other}
|
||||
msg_sitting_down: Successfully sit down
|
||||
|
|
@ -207,13 +207,13 @@ class Sittable(DefaultObject):
|
|||
"""
|
||||
def at_object_creation(self):
|
||||
self.db.sitter = None
|
||||
# do you sit "on" or "in" this object?
|
||||
# do you sit "on" or "in" this object?
|
||||
self.db.adjective = "on"
|
||||
|
||||
def do_sit(self, sitter):
|
||||
"""
|
||||
Called when trying to sit on/in this object.
|
||||
|
||||
Called when trying to sit on/in this object.
|
||||
|
||||
Args:
|
||||
sitter (Object): The one trying to sit down.
|
||||
|
||||
|
|
@ -235,7 +235,7 @@ class Sittable(DefaultObject):
|
|||
else:
|
||||
sitter.msg(f"You can't sit {adjective} {self.key} "
|
||||
f"- {current.key} is already sitting there!")
|
||||
return
|
||||
return
|
||||
self.db.sitting = sitter
|
||||
sitter.db.is_resting = True
|
||||
if self.db.msg_sitting_down:
|
||||
|
|
@ -245,13 +245,13 @@ class Sittable(DefaultObject):
|
|||
|
||||
def do_stand(self, stander):
|
||||
"""
|
||||
Called when trying to stand from this object.
|
||||
Called when trying to stand from this object.
|
||||
|
||||
Args:
|
||||
stander (Object): The one trying to stand up.
|
||||
|
||||
"""
|
||||
current = self.db.sitter
|
||||
current = self.db.sitter
|
||||
if not stander == current:
|
||||
if self.db.msg_standing_fail:
|
||||
stander.msg(self.db.msg_standing_fail.format(
|
||||
|
|
@ -260,7 +260,7 @@ class Sittable(DefaultObject):
|
|||
stander.msg(f"You are not sitting {self.db.adjective} {self.key}")
|
||||
else:
|
||||
self.db.sitting = None
|
||||
stander.db.is_resting = False
|
||||
stander.db.is_resting = False
|
||||
if self.db.msg_standing_up:
|
||||
stander.msg(self.db.msg_standing_up.format(
|
||||
adjective=self.db.adjective, key=self.key))
|
||||
|
|
@ -268,55 +268,55 @@ class Sittable(DefaultObject):
|
|||
stander.msg(f"You stand up from {self.key}")
|
||||
```
|
||||
|
||||
Here we really went all out with flexibility. If you need this much is up to you.
|
||||
We added a bunch of optional Attributes to hold alternative versions of all the messages.
|
||||
There are some things to note:
|
||||
Here we really went all out with flexibility. If you need this much is up to you.
|
||||
We added a bunch of optional Attributes to hold alternative versions of all the messages.
|
||||
There are some things to note:
|
||||
|
||||
- We don't actually initiate those Attributes in `at_object_creation`. This is a simple
|
||||
optimization. The assumption is that _most_ chairs will probably not be this customized.
|
||||
So initiating a bunch of Attributes to, say, empty strings would be a lot of useless database calls.
|
||||
- We don't actually initiate those Attributes in `at_object_creation`. This is a simple
|
||||
optimization. The assumption is that _most_ chairs will probably not be this customized.
|
||||
So initiating a bunch of Attributes to, say, empty strings would be a lot of useless database calls.
|
||||
The drawback is that the available Attributes become less visible when reading the code. So we add a long
|
||||
describing docstring to the end to explain all you can use.
|
||||
describing docstring to the end to explain all you can use.
|
||||
- We use `.format` to inject formatting-tokens in the text. The good thing about such formatting
|
||||
markers is that they are _optional_. They are there if you want them, but Python will not complain
|
||||
if you don't include some or any of them. Let's see an example:
|
||||
markers is that they are _optional_. They are there if you want them, but Python will not complain
|
||||
if you don't include some or any of them. Let's see an example:
|
||||
|
||||
> reload # if you have new code
|
||||
> create/drop armchair : sittables.Sittable
|
||||
> create/drop armchair : sittables.Sittable
|
||||
> set armchair/adjective = in
|
||||
> set armchair/msg_sitting_down = As you sit down {adjective} {key}, life feels easier.
|
||||
> set armchair/msg_standing_up = You stand up from {key}. Life resumes.
|
||||
|
||||
The `{key}` and `{adjective}` are examples of optional formatting markers. Whenever the message is
|
||||
returned, the format-tokens within will be replaced with `armchair` and `in` respectively. Should we
|
||||
|
||||
The `{key}` and `{adjective}` are examples of optional formatting markers. Whenever the message is
|
||||
returned, the format-tokens within will be replaced with `armchair` and `in` respectively. Should we
|
||||
rename the chair later, this will show in the messages automatically (since `{key}` will change).
|
||||
|
||||
We have no Command to use this chair yet. But we can try it out with `py`:
|
||||
|
||||
> py self.search("armchair").do_sit(self)
|
||||
As you sit down in armchair, life feels easier.
|
||||
> self.db.resting
|
||||
As you sit down in armchair, life feels easier.
|
||||
> self.db.resting
|
||||
True
|
||||
> py self.search("armchair").do_stand(self)
|
||||
You stand up from armchair. Life resumes
|
||||
> self.db.resting
|
||||
False
|
||||
|
||||
If you follow along and get a result like this, all seems to be working well!
|
||||
You stand up from armchair. Life resumes
|
||||
> self.db.resting
|
||||
False
|
||||
|
||||
If you follow along and get a result like this, all seems to be working well!
|
||||
|
||||
## Command variant 1: Commands on the chair
|
||||
|
||||
This way to implement `sit` and `stand` puts new cmdsets on the Sittable itself.
|
||||
As we've learned before, commands on objects are made available to others in the room.
|
||||
This makes the command easy but instead adds some complexity in the management of the CmdSet.
|
||||
As we've learned before, commands on objects are made available to others in the room.
|
||||
This makes the command easy but instead adds some complexity in the management of the CmdSet.
|
||||
|
||||
This is how it will look if `armchair` is in the room:
|
||||
This is how it will look if `armchair` is in the room:
|
||||
|
||||
> sit
|
||||
As you sit down in armchair, life feels easier.
|
||||
> sit
|
||||
As you sit down in armchair, life feels easier.
|
||||
|
||||
What happens if there are sittables `sofa` and `barstool` also in the room? Evennia will automatically
|
||||
handle this for us and allow us to specify which one we want:
|
||||
handle this for us and allow us to specify which one we want:
|
||||
|
||||
> sit
|
||||
More than one match for 'sit' (please narrow target):
|
||||
|
|
@ -325,24 +325,24 @@ handle this for us and allow us to specify which one we want:
|
|||
sit-3 (barstool)
|
||||
> sit-1
|
||||
As you sit down in armchair, life feels easier.
|
||||
|
||||
|
||||
To keep things separate we'll make a new module `mygame/commands/sittables.py`:
|
||||
|
||||
```sidebar:: Separate Commands and Typeclasses?
|
||||
|
||||
```{sidebar} Separate Commands and Typeclasses?
|
||||
|
||||
You can organize these things as you like. If you wanted you could put the sit-command + cmdset
|
||||
together with the `Sittable` typeclass in `mygame/typeclasses/sittables.py`. That has the advantage of
|
||||
keeping everything related to sitting in one place. But there is also some organizational merit to
|
||||
keeping all Commands in one place as we do here.
|
||||
together with the `Sittable` typeclass in `mygame/typeclasses/sittables.py`. That has the advantage of
|
||||
keeping everything related to sitting in one place. But there is also some organizational merit to
|
||||
keeping all Commands in one place as we do here.
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
from evennia import Command, CmdSet
|
||||
|
||||
class CmdSit(Command):
|
||||
class CmdSit(Command):
|
||||
"""
|
||||
Sit down.
|
||||
Sit down.
|
||||
"""
|
||||
key = "sit"
|
||||
|
||||
|
|
@ -366,65 +366,65 @@ class CmdSetSit(CmdSet):
|
|||
|
||||
```
|
||||
|
||||
As seen, the commands are nearly trivial. `self.obj` is the object to which we added the cmdset with this
|
||||
Command (so for example a chair). We just call the `do_sit/stand` on that object and the `Sittable` will
|
||||
As seen, the commands are nearly trivial. `self.obj` is the object to which we added the cmdset with this
|
||||
Command (so for example a chair). We just call the `do_sit/stand` on that object and the `Sittable` will
|
||||
do the rest.
|
||||
|
||||
Why that `priority = 1` on `CmdSetSit`? This makes same-named Commands from this cmdset merge with a bit higher
|
||||
priority than Commands from the Character-cmdset. Why this is a good idea will become clear shortly.
|
||||
|
||||
priority than Commands from the Character-cmdset. Why this is a good idea will become clear shortly.
|
||||
|
||||
We also need to make a change to our `Sittable` typeclass. Open `mygame/typeclasses/sittables.py`:
|
||||
|
||||
|
||||
```python
|
||||
from evennia import DefaultObject
|
||||
from commands.sittables import CmdSetSit # <- new
|
||||
from commands.sittables import CmdSetSit # <- new
|
||||
|
||||
class Sittable(DefaultObject):
|
||||
class Sittable(DefaultObject):
|
||||
"""
|
||||
(docstring)
|
||||
"""
|
||||
"""
|
||||
def at_object_creation(self):
|
||||
|
||||
self.db.sitter = None
|
||||
# do you sit "on" or "in" this object?
|
||||
# do you sit "on" or "in" this object?
|
||||
self.db.adjective = "on"
|
||||
self.cmdset.add_default(CmdSetSit) # <- new
|
||||
```
|
||||
```
|
||||
|
||||
Any _new_ Sittables will now have your `sit` Command. Your existing `armchair` will not,
|
||||
since `at_object_creation` will not re-run for already existing objects. We can update it manually:
|
||||
Any _new_ Sittables will now have your `sit` Command. Your existing `armchair` will not,
|
||||
since `at_object_creation` will not re-run for already existing objects. We can update it manually:
|
||||
|
||||
> reload
|
||||
> update armchair
|
||||
|
||||
> reload
|
||||
> update armchair
|
||||
|
||||
We could also update all existing sittables (all on one line):
|
||||
|
||||
> py from typeclasses.sittables import Sittable ;
|
||||
> py from typeclasses.sittables import Sittable ;
|
||||
[sittable.at_object_creation() for sittable in Sittable.objects.all()]
|
||||
|
||||
> The above shows an example of a _list comprehension_. Think of it as an efficient way to construct a new list
|
||||
all in one line. You can read more about list comprehensions
|
||||
|
||||
> The above shows an example of a _list comprehension_. Think of it as an efficient way to construct a new list
|
||||
all in one line. You can read more about list comprehensions
|
||||
[here in the Python docs](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions).
|
||||
|
||||
|
||||
We should now be able to use `sit` while in the room with the armchair.
|
||||
|
||||
> sit
|
||||
> sit
|
||||
As you sit down in armchair, life feels easier.
|
||||
> stand
|
||||
> stand
|
||||
You stand up from armchair.
|
||||
|
||||
One issue with placing the `sit` (or `stand`) Command "on" the chair is that it will not be available when in a
|
||||
room without a Sittable object:
|
||||
One issue with placing the `sit` (or `stand`) Command "on" the chair is that it will not be available when in a
|
||||
room without a Sittable object:
|
||||
|
||||
> sit
|
||||
> sit
|
||||
Command 'sit' is not available. ...
|
||||
|
||||
This is practical but not so good-looking; it makes it harder for the user to know a `sit` action is at all
|
||||
possible. Here is a trick for fixing this. Let's add _another_ Command to the bottom
|
||||
|
||||
This is practical but not so good-looking; it makes it harder for the user to know a `sit` action is at all
|
||||
possible. Here is a trick for fixing this. Let's add _another_ Command to the bottom
|
||||
of `mygame/commands/sittables.py`:
|
||||
|
||||
```python
|
||||
# ...
|
||||
# ...
|
||||
|
||||
class CmdNoSitStand(Command):
|
||||
"""
|
||||
|
|
@ -441,15 +441,15 @@ class CmdNoSitStand(Command):
|
|||
|
||||
```
|
||||
|
||||
Here we have a Command that is actually two - it will answer to both `sit` and `stand` since we
|
||||
added `stand` to its `aliases`. In the command we look at `self.cmdname`, which is the string
|
||||
_actually used_ to call this command. We use this to return different messages.
|
||||
Here we have a Command that is actually two - it will answer to both `sit` and `stand` since we
|
||||
added `stand` to its `aliases`. In the command we look at `self.cmdname`, which is the string
|
||||
_actually used_ to call this command. We use this to return different messages.
|
||||
|
||||
We don't need a separate CmdSet for this, instead we will add this
|
||||
We don't need a separate CmdSet for this, instead we will add this
|
||||
to the default Character cmdset. Open `mygame/commands/default_cmdsets.py`:
|
||||
|
||||
```python
|
||||
# ...
|
||||
# ...
|
||||
from commands import sittables
|
||||
|
||||
class CharacterCmdSet(CmdSet):
|
||||
|
|
@ -462,36 +462,36 @@ class CharacterCmdSet(CmdSet):
|
|||
|
||||
```
|
||||
|
||||
To test we'll build a new location without any comfy armchairs and go there:
|
||||
To test we'll build a new location without any comfy armchairs and go there:
|
||||
|
||||
> reload
|
||||
> tunnel n = kitchen
|
||||
north
|
||||
> sit
|
||||
> reload
|
||||
> tunnel n = kitchen
|
||||
north
|
||||
> sit
|
||||
You have nothing to sit on.
|
||||
> south
|
||||
sit
|
||||
> south
|
||||
sit
|
||||
As you sit down in armchair, life feels easier.
|
||||
|
||||
We now have a fully functioning `sit` action that is contained with the chair itself. When no chair is around, a
|
||||
default error message is shown.
|
||||
|
||||
How does this work? There are two cmdsets at play, both of which have a `sit` Command. As you may remember we
|
||||
set the chair's cmdset to `priority = 1`. This is where that matters. The default Character cmdset has a
|
||||
We now have a fully functioning `sit` action that is contained with the chair itself. When no chair is around, a
|
||||
default error message is shown.
|
||||
|
||||
How does this work? There are two cmdsets at play, both of which have a `sit` Command. As you may remember we
|
||||
set the chair's cmdset to `priority = 1`. This is where that matters. The default Character cmdset has a
|
||||
priority of 0. This means that whenever we enter a room with a Sittable thing, the `sit` command
|
||||
from _its_ cmdset will take _precedence_ over the Character cmdset's version. So we are actually picking
|
||||
from _its_ cmdset will take _precedence_ over the Character cmdset's version. So we are actually picking
|
||||
_different_ `sit` commands depending on circumstance! The user will never be the wiser.
|
||||
|
||||
So this handles `sit`. What about `stand`? That will work just fine:
|
||||
|
||||
So this handles `sit`. What about `stand`? That will work just fine:
|
||||
|
||||
> stand
|
||||
You stand up from armchair.
|
||||
You stand up from armchair.
|
||||
> north
|
||||
> stand
|
||||
You are not sitting down.
|
||||
|
||||
We have one remaining problem with `stand` though - what happens when you are sitting down and try to
|
||||
`stand` in a room with more than one chair:
|
||||
> stand
|
||||
You are not sitting down.
|
||||
|
||||
We have one remaining problem with `stand` though - what happens when you are sitting down and try to
|
||||
`stand` in a room with more than one chair:
|
||||
|
||||
> stand
|
||||
More than one match for 'stand' (please narrow target):
|
||||
|
|
@ -499,12 +499,12 @@ We have one remaining problem with `stand` though - what happens when you are si
|
|||
stand-2 (sofa)
|
||||
stand-3 (barstool)
|
||||
|
||||
Since all the sittables have the `stand` Command on them, you'll get a multi-match error. This _works_ ... but
|
||||
Since all the sittables have the `stand` Command on them, you'll get a multi-match error. This _works_ ... but
|
||||
you could pick _any_ of those sittables to "stand up from". That's really weird and non-intuitive. With `sit` it
|
||||
was okay to get a choice - Evennia can't know which chair we intended to sit on. But we know which chair we
|
||||
sit on so we should only get _its_ `stand` command.
|
||||
was okay to get a choice - Evennia can't know which chair we intended to sit on. But we know which chair we
|
||||
sit on so we should only get _its_ `stand` command.
|
||||
|
||||
We will fix this with a `lock` and a custom `lock function`. We want a lock on the `stand` Command that only
|
||||
We will fix this with a `lock` and a custom `lock function`. We want a lock on the `stand` Command that only
|
||||
makes it available when the caller is actually sitting on the chair the `stand` command is on.
|
||||
|
||||
First let's add the lock so we see what we want. Open `mygame/commands/sittables.py`:
|
||||
|
|
@ -518,37 +518,37 @@ class CmdStand(Command):
|
|||
"""
|
||||
key = "stand"
|
||||
lock = "cmd:sitsonthis()" # < this is new
|
||||
|
||||
|
||||
def func(self):
|
||||
self.obj.do_stand(self.caller)
|
||||
# ...
|
||||
```
|
||||
|
||||
We define a [Lock](../../../Components/Locks) on the command. The `cmd:` is in what situation Evennia will check
|
||||
the lock. The `cmd` means that it will check the lock when determining if a user has access to this command or not.
|
||||
What will be checked is the `sitsonthis` _lock function_ which doesn't exist yet.
|
||||
We define a [Lock](../../../Components/Locks.md) on the command. The `cmd:` is in what situation Evennia will check
|
||||
the lock. The `cmd` means that it will check the lock when determining if a user has access to this command or not.
|
||||
What will be checked is the `sitsonthis` _lock function_ which doesn't exist yet.
|
||||
|
||||
Open `mygame/server/conf/lockfuncs.py` to add it!
|
||||
Open `mygame/server/conf/lockfuncs.py` to add it!
|
||||
|
||||
```python
|
||||
"""
|
||||
(module lockstring)
|
||||
"""
|
||||
# ...
|
||||
# ...
|
||||
|
||||
def sitsonthis(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
True if accessing_obj is sitting on/in the accessed_obj.
|
||||
True if accessing_obj is sitting on/in the accessed_obj.
|
||||
"""
|
||||
return accessed_obj.db.sitting == accessing_obj
|
||||
|
||||
# ...
|
||||
# ...
|
||||
```
|
||||
|
||||
Evennia knows that all functions in `mygame/server/conf/lockfuncs` should be possible to use in a lock definition.
|
||||
The arguments are required and Evennia will pass all relevant objects to them:
|
||||
The arguments are required and Evennia will pass all relevant objects to them:
|
||||
|
||||
```sidebar:: Lockfuncs
|
||||
```{sidebar} Lockfuncs
|
||||
|
||||
Evennia provides a large number of default lockfuncs, such as checking permission-levels,
|
||||
if you are carrying or are inside the accessed object etc. There is no concept of 'sitting'
|
||||
|
|
@ -556,53 +556,53 @@ The arguments are required and Evennia will pass all relevant objects to them:
|
|||
|
||||
```
|
||||
|
||||
- `accessing_obj` is the one trying to access the lock. So us, in this case.
|
||||
- `accessing_obj` is the one trying to access the lock. So us, in this case.
|
||||
- `accessed_obj` is the entity we are trying to gain a particular type of access to. So the chair.
|
||||
- `args` is a tuple holding any arguments passed to the lockfunc. Since we use `sitsondthis()` this will
|
||||
- `args` is a tuple holding any arguments passed to the lockfunc. Since we use `sitsondthis()` this will
|
||||
be empty (and if we add anything, it will be ignored).
|
||||
- `kwargs` is a tuple of keyword arguments passed to the lockfuncs. This will be empty as well in our example.
|
||||
|
||||
If you are superuser, it's important that you `quell` yourself before trying this out. This is because the superuser
|
||||
If you are superuser, it's important that you `quell` yourself before trying this out. This is because the superuser
|
||||
bypasses all locks - it can never get locked out, but it means it will also not see the effects of a lock like this.
|
||||
|
||||
> reload
|
||||
> quell
|
||||
> stand
|
||||
You stand up from armchair
|
||||
|
||||
None of the other sittables' `stand` commands passed the lock and only the one we are actually sitting on did.
|
||||
> reload
|
||||
> quell
|
||||
> stand
|
||||
You stand up from armchair
|
||||
|
||||
None of the other sittables' `stand` commands passed the lock and only the one we are actually sitting on did.
|
||||
|
||||
Adding a Command to the chair object like this is powerful and a good technique to know. It does come with some
|
||||
caveats though that one needs to keep in mind.
|
||||
caveats though that one needs to keep in mind.
|
||||
|
||||
We'll now try another way to add the `sit/stand` commands.
|
||||
|
||||
## Command variant 2: Command on Character
|
||||
## Command variant 2: Command on Character
|
||||
|
||||
Before we start with this, delete the chairs you've created (`del armchair` etc) and then do the following
|
||||
changes:
|
||||
Before we start with this, delete the chairs you've created (`del armchair` etc) and then do the following
|
||||
changes:
|
||||
|
||||
- In `mygame/typeclasses/sittables.py`, comment out the line `self.cmdset.add_default(CmdSetSit)`.
|
||||
- In `mygame/commands/default_cmdsets.py`, comment out the line `self.add(sittables.CmdNoSitStand)`.
|
||||
- In `mygame/commands/default_cmdsets.py`, comment out the line `self.add(sittables.CmdNoSitStand)`.
|
||||
|
||||
This disables the on-object command solution so we can try an alternative. Make sure to `reload` so the
|
||||
changes are known to Evennia.
|
||||
This disables the on-object command solution so we can try an alternative. Make sure to `reload` so the
|
||||
changes are known to Evennia.
|
||||
|
||||
In this variation we will put the `sit` and `stand` commands on the `Character` instead of on the chair. This
|
||||
makes some things easier, but makes the Commands themselves more complex because they will not know which
|
||||
chair to sit on. We can't just do `sit` anymore. This is how it will work.
|
||||
makes some things easier, but makes the Commands themselves more complex because they will not know which
|
||||
chair to sit on. We can't just do `sit` anymore. This is how it will work.
|
||||
|
||||
> sit <chair>
|
||||
> sit <chair>
|
||||
You sit on chair.
|
||||
> stand
|
||||
> stand
|
||||
You stand up from chair.
|
||||
|
||||
Open `mygame/commands.sittables.py` again. We'll add a new sit-command. We name the class `CmdSit2` since
|
||||
we already have `CmdSit` from the previous example. We put everything at the end of the module to
|
||||
keep it separate.
|
||||
|
||||
Open `mygame/commands.sittables.py` again. We'll add a new sit-command. We name the class `CmdSit2` since
|
||||
we already have `CmdSit` from the previous example. We put everything at the end of the module to
|
||||
keep it separate.
|
||||
|
||||
```python
|
||||
from evennia import Command, CmdSet
|
||||
from evennia import Command, CmdSet
|
||||
from evennia import InterruptCommand # <- this is new
|
||||
|
||||
class CmdSit(Command):
|
||||
|
|
@ -610,18 +610,18 @@ class CmdSit(Command):
|
|||
|
||||
# ...
|
||||
|
||||
# new from here
|
||||
# new from here
|
||||
|
||||
class CmdSit2(Command):
|
||||
"""
|
||||
Sit down.
|
||||
|
||||
Usage:
|
||||
Usage:
|
||||
sit <sittable>
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
key = "sit"
|
||||
|
||||
|
||||
def parse(self):
|
||||
self.args = self.args.strip()
|
||||
if not self.args:
|
||||
|
|
@ -641,7 +641,7 @@ class CmdSit2(Command):
|
|||
|
||||
```
|
||||
|
||||
With this Command-variation we need to search for the sittable. A series of methods on the Command
|
||||
With this Command-variation we need to search for the sittable. A series of methods on the Command
|
||||
are run in sequence:
|
||||
|
||||
1. `Command.at_pre_command` - this is not used by default
|
||||
|
|
@ -649,77 +649,77 @@ are run in sequence:
|
|||
3. `Command.func` - this should implement the actual Command functionality
|
||||
4. `Command.at_post_func` - this is not used by default
|
||||
|
||||
So if we just `return` in `.parse`, `.func` will still run, which is not what we want. To immediately
|
||||
So if we just `return` in `.parse`, `.func` will still run, which is not what we want. To immediately
|
||||
abort this sequence we need to `raise InterruptCommand`.
|
||||
|
||||
```sidebar:: Raising exceptions
|
||||
```{sidebar} Raising exceptions
|
||||
|
||||
Raising an exception allows for immediately interrupting the current program flow. Python
|
||||
automatically raises error-exceptions when detecting problems with the code. It will be
|
||||
Raising an exception allows for immediately interrupting the current program flow. Python
|
||||
automatically raises error-exceptions when detecting problems with the code. It will be
|
||||
raised up through the sequence of called code (the 'stack') until it's either `caught` with
|
||||
a `try ... except` or reaches the outermost scope where it'll be logged or displayed.
|
||||
|
||||
```
|
||||
|
||||
`InterruptCommand` is an _exception_ that the Command-system catches with the understanding that we want
|
||||
to do a clean abort. In the `.parse` method we strip any whitespaces from the argument and
|
||||
sure there actuall _is_ an argument. We abort immediately if there isn't.
|
||||
`InterruptCommand` is an _exception_ that the Command-system catches with the understanding that we want
|
||||
to do a clean abort. In the `.parse` method we strip any whitespaces from the argument and
|
||||
sure there actuall _is_ an argument. We abort immediately if there isn't.
|
||||
|
||||
We we get to `.func` at all, we know that we have an argument. We search for this and abort if we there was
|
||||
a problem finding the target.
|
||||
a problem finding the target.
|
||||
|
||||
> We could have done `raise InterruptCommand` in `.func` as well, but `return` is a little shorter to write
|
||||
> and there is no harm done if `at_post_func` runs since it's empty.
|
||||
|
||||
Next we call the found sittable's `do_sit` method. Note that we wrap this call like this:
|
||||
Next we call the found sittable's `do_sit` method. Note that we wrap this call like this:
|
||||
|
||||
```python
|
||||
|
||||
try:
|
||||
# code
|
||||
# code
|
||||
except AttributeError:
|
||||
# stuff to do if AttributeError exception was raised
|
||||
```
|
||||
|
||||
The reason is that `caller.search` has no idea we are looking for a Sittable. The user could have tried
|
||||
The reason is that `caller.search` has no idea we are looking for a Sittable. The user could have tried
|
||||
`sit wall` or `sit sword`. These don't have a `do_sit` method _but we call it anyway and handle the error_.
|
||||
This is a very "Pythonic" thing to do. The concept is often called "leap before you look" or "it's easier to
|
||||
ask for forgiveness than for permission". If `sittable.do_sit` does not exist, Python will raise an `AttributeError`.
|
||||
We catch this with `try ... except AttributeError` and convert it to a proper error message.
|
||||
We catch this with `try ... except AttributeError` and convert it to a proper error message.
|
||||
|
||||
While it's useful to learn about `try ... except`, there is also a way to leverage Evennia to do this without
|
||||
`try ... except`:
|
||||
|
||||
```python
|
||||
|
||||
# ...
|
||||
# ...
|
||||
|
||||
def func(self):
|
||||
|
||||
# self.search handles all error messages etc.
|
||||
sittable = self.caller.search(
|
||||
self.args,
|
||||
self.args,
|
||||
typeclass="typeclasses.sittables.Sittable")
|
||||
if not sittable:
|
||||
return
|
||||
sittable.do_sit(self.caller)
|
||||
```
|
||||
|
||||
```sidebar:: Continuing across multiple lines
|
||||
```{sidebar} Continuing across multiple lines
|
||||
|
||||
Note how the `.search()` method's arguments are spread out over multiple
|
||||
lines. This works for all lists, tuples and other listings and is
|
||||
lines. This works for all lists, tuples and other listings and is
|
||||
a good way to avoid very long and hard-to-read lines.
|
||||
|
||||
```
|
||||
|
||||
The `caller.search` method has an keyword argument `typeclass` that can take either a python-path to a
|
||||
The `caller.search` method has an keyword argument `typeclass` that can take either a python-path to a
|
||||
typeclass, the typeclass itself, or a list of either to widen the allowed options. In this case we know
|
||||
for sure that the `sittable` we get is actually a `Sittable` class and we can call `sittable.do_sit` without
|
||||
needing to worry about catching errors.
|
||||
needing to worry about catching errors.
|
||||
|
||||
Let's do the `stand` command while we are at it. Again, since the Command is external to the chair we don't
|
||||
know which object we are sitting in and have to search for it.
|
||||
know which object we are sitting in and have to search for it.
|
||||
|
||||
```python
|
||||
|
||||
|
|
@ -727,43 +727,43 @@ class CmdStand2(Command):
|
|||
"""
|
||||
Stand up.
|
||||
|
||||
Usage:
|
||||
Usage:
|
||||
stand
|
||||
|
||||
"""
|
||||
|
||||
"""
|
||||
key = "stand"
|
||||
|
||||
|
||||
def func(self):
|
||||
|
||||
caller = self.caller
|
||||
caller = self.caller
|
||||
# find the thing we are sitting on/in, by finding the object
|
||||
# in the current location that as an Attribute "sitter" set
|
||||
# in the current location that as an Attribute "sitter" set
|
||||
# to the caller
|
||||
sittable = caller.search(
|
||||
caller,
|
||||
caller,
|
||||
candidates=caller.location.contents,
|
||||
attribute_name="sitter",
|
||||
attribute_name="sitter",
|
||||
typeclass="typeclasses.sittables.Sittable")
|
||||
# if this is None, the error was already reported to user
|
||||
if not sittable:
|
||||
return
|
||||
return
|
||||
|
||||
sittable.do_stand(caller)
|
||||
|
||||
|
||||
```
|
||||
|
||||
This forced us to to use the full power of the `caller.search` method. If we wanted to search for something
|
||||
more complex we would likely need to break out a [Django query](../Part1/Django-queries) to do it. The key here is that
|
||||
we know that the object we are looking for is a `Sittable` and that it must have an Attribute named `sitter`
|
||||
which should be set to us, the one sitting on/in the thing. Once we have that we just call `.do_stand` on it
|
||||
and let the Typeclass handle the rest.
|
||||
This forced us to to use the full power of the `caller.search` method. If we wanted to search for something
|
||||
more complex we would likely need to break out a [Django query](../Part1/Django-queries.md) to do it. The key here is that
|
||||
we know that the object we are looking for is a `Sittable` and that it must have an Attribute named `sitter`
|
||||
which should be set to us, the one sitting on/in the thing. Once we have that we just call `.do_stand` on it
|
||||
and let the Typeclass handle the rest.
|
||||
|
||||
All that is left now is to make this available to us. This type of Command should be available to us all the time
|
||||
so we can put it in the default Cmdset` on the Character. Open `mygame/default_cmdsets.py`
|
||||
|
||||
|
||||
```python
|
||||
# ...
|
||||
# ...
|
||||
from commands import sittables
|
||||
|
||||
class CharacterCmdSet(CmdSet):
|
||||
|
|
@ -777,26 +777,26 @@ class CharacterCmdSet(CmdSet):
|
|||
|
||||
```
|
||||
|
||||
Now let's try it out:
|
||||
Now let's try it out:
|
||||
|
||||
> reload
|
||||
> reload
|
||||
> create/drop sofa : sittables.Sittable
|
||||
> sit sofa
|
||||
You sit down on sofa.
|
||||
> stand
|
||||
> stand
|
||||
You stand up from sofa.
|
||||
|
||||
|
||||
## Conclusions
|
||||
|
||||
In this lesson we accomplished quite a bit:
|
||||
In this lesson we accomplished quite a bit:
|
||||
|
||||
- We modified our `Character` class to avoid moving when sitting down.
|
||||
- We made a new `Sittable` typeclass
|
||||
- We tried two ways to allow a user to interact with sittables using `sit` and `stand` commands.
|
||||
- We tried two ways to allow a user to interact with sittables using `sit` and `stand` commands.
|
||||
|
||||
Eagle-eyed readers will notice that the `stand` command sitting "on" the chair (variant 1) would work just fine
|
||||
together with the `sit` command sitting "on" the Character (variant 2). There is nothing stopping you from
|
||||
mixing them, or even try a third solution that better fits what you have in mind.
|
||||
Eagle-eyed readers will notice that the `stand` command sitting "on" the chair (variant 1) would work just fine
|
||||
together with the `sit` command sitting "on" the Character (variant 2). There is nothing stopping you from
|
||||
mixing them, or even try a third solution that better fits what you have in mind.
|
||||
|
||||
[prev lesson](../../../Unimplemented) | [next lesson](../../../Unimplemented)
|
||||
[prev lesson](../../../Unimplemented.md) | [next lesson](../../../Unimplemented.md)
|
||||
|
|
|
|||
|
|
@ -45,12 +45,12 @@ makes it easier to change and update things in one place later.
|
|||
values for Health, a list of skills etc, store those things on the Character - don't store how to
|
||||
roll or change them.
|
||||
- Next is to determine just how you want to store things on your Objects and Characters. You can
|
||||
choose to either store things as individual [Attributes](../../../Components/Attributes), like `character.db.STR=34` and
|
||||
choose to either store things as individual [Attributes](../../../Components/Attributes.md), like `character.db.STR=34` and
|
||||
`character.db.Hunting_skill=20`. But you could also use some custom storage method, like a
|
||||
dictionary `character.db.skills = {"Hunting":34, "Fishing":20, ...}`. A much more fancy solution is
|
||||
to look at the Ainneve [Trait
|
||||
handler](https://github.com/evennia/ainneve/blob/master/world/traits.py). Finally you could even go
|
||||
with a [custom django model](../../../Concepts/New-Models). Which is the better depends on your game and the
|
||||
with a [custom django model](../../../Concepts/New-Models.md). Which is the better depends on your game and the
|
||||
complexity of your system.
|
||||
- Make a clear [API](https://en.wikipedia.org/wiki/Application_programming_interface) into your
|
||||
rules. That is, make methods/functions that you feed with, say, your Character and which skill you
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# Evennia Starting Tutorial (Part 3)
|
||||
|
||||
```sidebar:: Tutorial Parts
|
||||
```{eval-rst}
|
||||
|
||||
sidebar:: Tutorial Parts
|
||||
|
||||
Part 1: `What we have <../Part1/Starting-Part1.html>`_
|
||||
A tour of Evennia and how to use the tools, including an introduction to Python.
|
||||
|
|
@ -13,33 +15,33 @@
|
|||
Part 5: `Showing the world <../Part5/Starting-Part5.html>`_
|
||||
Taking our new game online and let players try it out
|
||||
```
|
||||
In part three of the Evennia Starting tutorial we will go through the creation of several key parts
|
||||
of our tutorial game _EvAdventure_. As we go, we will test each part and create a simple "tech demo" to
|
||||
show off all the moving parts.
|
||||
In part three of the Evennia Starting tutorial we will go through the creation of several key parts
|
||||
of our tutorial game _EvAdventure_. As we go, we will test each part and create a simple "tech demo" to
|
||||
show off all the moving parts.
|
||||
|
||||
1. Introduction & Overview (you are here)
|
||||
1. [Changing settings](../../../Unimplemented)
|
||||
1. [Applying contribs](../../../Unimplemented)
|
||||
1. [Creating a rule module](../../../Unimplemented)
|
||||
1. [Tweaking the base Typeclasses](../../../Unimplemented)
|
||||
1. [Character creation menu](../../../Unimplemented)
|
||||
1. [Wearing armor and wielding weapons](../../../Unimplemented)
|
||||
1. [Two types of combat](../../../Unimplemented)
|
||||
1. [Monsters and AI](../../../Unimplemented)
|
||||
1. [Questing and rewards](../../../Unimplemented)
|
||||
1. [Overview of Tech demo](../../../Unimplemented)
|
||||
1. [Changing settings](../../../Unimplemented.md)
|
||||
1. [Applying contribs](../../../Unimplemented.md)
|
||||
1. [Creating a rule module](../../../Unimplemented.md)
|
||||
1. [Tweaking the base Typeclasses](../../../Unimplemented.md)
|
||||
1. [Character creation menu](../../../Unimplemented.md)
|
||||
1. [Wearing armor and wielding weapons](../../../Unimplemented.md)
|
||||
1. [Two types of combat](../../../Unimplemented.md)
|
||||
1. [Monsters and AI](../../../Unimplemented.md)
|
||||
1. [Questing and rewards](../../../Unimplemented.md)
|
||||
1. [Overview of Tech demo](../../../Unimplemented.md)
|
||||
|
||||
If you followed the previous parts of this tutorial you will have some notions about Python and where to find
|
||||
and make use of things in Evennia. We also have a good idea of the type of game we want.
|
||||
Even if this is not the game-style you are interested in, following along will give you a lot of experience
|
||||
with using Evennia. This be of much use when doing your own thing later.
|
||||
and make use of things in Evennia. We also have a good idea of the type of game we want.
|
||||
Even if this is not the game-style you are interested in, following along will give you a lot of experience
|
||||
with using Evennia. This be of much use when doing your own thing later.
|
||||
|
||||
_TODO_
|
||||
|
||||
|
||||
```toctree::
|
||||
:hidden:
|
||||
```{toctree}
|
||||
:hidden:
|
||||
|
||||
../../../Unimplemented
|
||||
../../../Unimplemented
|
||||
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ allows for emoting as part of combat which is an advantage for roleplay-heavy ga
|
|||
To implement a freeform combat system all you need is a dice roller and a roleplaying rulebook. See
|
||||
[contrib/dice.py](https://github.com/evennia/evennia/blob/master/evennia/contrib/dice.py) for an
|
||||
example dice roller. To implement at twitch-based system you basically need a few combat
|
||||
[commands](../../../Components/Commands), possibly ones with a [cooldown](../../Command-Cooldown). You also need a [game rule
|
||||
module](Implementing-a-game-rule-system) that makes use of it. We will focus on the turn-based
|
||||
[commands](../../../Components/Commands.md), possibly ones with a [cooldown](../../Command-Cooldown.md). You also need a [game rule
|
||||
module](./Implementing-a-game-rule-system.md) that makes use of it. We will focus on the turn-based
|
||||
variety here.
|
||||
|
||||
## Tutorial overview
|
||||
|
|
@ -61,22 +61,22 @@ reported. A new turn then begins.
|
|||
|
||||
For creating the combat system we will need the following components:
|
||||
|
||||
- A combat handler. This is the main mechanic of the system. This is a [Script](../../../Components/Scripts) object
|
||||
- A combat handler. This is the main mechanic of the system. This is a [Script](../../../Components/Scripts.md) object
|
||||
created for each combat. It is not assigned to a specific object but is shared by the combating
|
||||
characters and handles all the combat information. Since Scripts are database entities it also means
|
||||
that the combat will not be affected by a server reload.
|
||||
- A combat [command set](../../../Components/Command-Sets) with the relevant commands needed for combat, such as the
|
||||
- A combat [command set](../../../Components/Command-Sets.md) with the relevant commands needed for combat, such as the
|
||||
various attack/defend options and the `flee/disengage` command to leave the combat mode.
|
||||
- A rule resolution system. The basics of making such a module is described in the [rule system
|
||||
tutorial](Implementing-a-game-rule-system). We will only sketch such a module here for our end-turn
|
||||
tutorial](./Implementing-a-game-rule-system.md). We will only sketch such a module here for our end-turn
|
||||
combat resolution.
|
||||
- An `attack` [command](../../../Components/Commands) for initiating the combat mode. This is added to the default
|
||||
- An `attack` [command](../../../Components/Commands.md) for initiating the combat mode. This is added to the default
|
||||
command set. It will create the combat handler and add the character(s) to it. It will also assign
|
||||
the combat command set to the characters.
|
||||
|
||||
## The combat handler
|
||||
|
||||
The _combat handler_ is implemented as a stand-alone [Script](../../../Components/Scripts). This Script is created when
|
||||
The _combat handler_ is implemented as a stand-alone [Script](../../../Components/Scripts.md). This Script is created when
|
||||
the first Character decides to attack another and is deleted when no one is fighting any more. Each
|
||||
handler represents one instance of combat and one combat only. Each instance of combat can hold any
|
||||
number of characters but each character can only be part of one combat at a time (a player would
|
||||
|
|
@ -89,7 +89,7 @@ don't use this very much here this might allow the combat commands on the charac
|
|||
update the combat handler state directly.
|
||||
|
||||
_Note: Another way to implement a combat handler would be to use a normal Python object and handle
|
||||
time-keeping with the [TickerHandler](../../../Components/TickerHandler). This would require either adding custom hook
|
||||
time-keeping with the [TickerHandler](../../../Components/TickerHandler.md). This would require either adding custom hook
|
||||
methods on the character or to implement a custom child of the TickerHandler class to track turns.
|
||||
Whereas the TickerHandler is easy to use, a Script offers more power in this case._
|
||||
|
||||
|
|
@ -507,7 +507,7 @@ class CmdAttack(Command):
|
|||
```
|
||||
|
||||
The `attack` command will not go into the combat cmdset but rather into the default cmdset. See e.g.
|
||||
the [Adding Command Tutorial](../Part1/Adding-Commands) if you are unsure about how to do this.
|
||||
the [Adding Command Tutorial](../Part1/Adding-Commands.md) if you are unsure about how to do this.
|
||||
|
||||
## Expanding the example
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ focused on free form storytelling. Even if you are not interested in MUSH:es, th
|
|||
first game-type to try since it's not so code heavy. You will be able to use the same principles for
|
||||
building other types of games.
|
||||
|
||||
The tutorial starts from scratch. If you did the [First Steps Coding](../Part1/Starting-Part1) tutorial
|
||||
The tutorial starts from scratch. If you did the [First Steps Coding](../Part1/Starting-Part1.md) tutorial
|
||||
already you should have some ideas about how to do some of the steps already.
|
||||
|
||||
The following are the (very simplistic and cut-down) features we will implement (this was taken from
|
||||
|
|
@ -61,7 +61,7 @@ class Character(DefaultCharacter):
|
|||
self.db.combat_score = 1
|
||||
```
|
||||
|
||||
We defined two new [Attributes](../../../Components/Attributes) `power` and `combat_score` and set them to default
|
||||
We defined two new [Attributes](../../../Components/Attributes.md) `power` and `combat_score` and set them to default
|
||||
values. Make sure to `@reload` the server if you had it already running (you need to reload every
|
||||
time you update your python code, don't worry, no accounts will be disconnected by the reload).
|
||||
|
||||
|
|
@ -94,8 +94,8 @@ check it. Using this method however will make it easy to add more functionality
|
|||
|
||||
What we need are the following:
|
||||
|
||||
- One character generation [Command](../../../Components/Commands) to set the "Power" on the `Character`.
|
||||
- A chargen [CmdSet](../../../Components/Command-Sets) to hold this command. Lets call it `ChargenCmdset`.
|
||||
- One character generation [Command](../../../Components/Commands.md) to set the "Power" on the `Character`.
|
||||
- A chargen [CmdSet](../../../Components/Command-Sets.md) to hold this command. Lets call it `ChargenCmdset`.
|
||||
- A custom `ChargenRoom` type that makes this set of commands available to players in such rooms.
|
||||
- One such room to test things in.
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ What we need are the following:
|
|||
For this tutorial we will add all our new commands to `mygame/commands/command.py` but you could
|
||||
split your commands into multiple module if you prefered.
|
||||
|
||||
For this tutorial character generation will only consist of one [Command](../../../Components/Commands) to set the
|
||||
For this tutorial character generation will only consist of one [Command](../../../Components/Commands.md) to set the
|
||||
Character s "power" stat. It will be called on the following MUSH-like form:
|
||||
|
||||
+setpower 4
|
||||
|
|
@ -156,7 +156,7 @@ This is a pretty straightforward command. We do some error checking, then set th
|
|||
We use a `help_category` of "mush" for all our commands, just so they are easy to find and separate
|
||||
in the help list.
|
||||
|
||||
Save the file. We will now add it to a new [CmdSet](../../../Components/Command-Sets) so it can be accessed (in a full
|
||||
Save the file. We will now add it to a new [CmdSet](../../../Components/Command-Sets.md) so it can be accessed (in a full
|
||||
chargen system you would of course have more than one command here).
|
||||
|
||||
Open `mygame/commands/default_cmdsets.py` and import your `command.py` module at the top. We also
|
||||
|
|
@ -210,7 +210,7 @@ class ChargenRoom(Room):
|
|||
```
|
||||
Note how new rooms created with this typeclass will always start with `ChargenCmdset` on themselves.
|
||||
Don't forget the `persistent=True` keyword or you will lose the cmdset after a server reload. For
|
||||
more information about [Command Sets](../../../Components/Command-Sets) and [Commands](../../../Components/Commands), see the respective
|
||||
more information about [Command Sets](../../../Components/Command-Sets.md) and [Commands](../../../Components/Commands.md), see the respective
|
||||
links.
|
||||
|
||||
### Testing chargen
|
||||
|
|
@ -242,7 +242,7 @@ between fixes. Don't continue until the creation seems to have worked okay.
|
|||
This should bring you to the chargen room. Being in there you should now have the `+setpower`
|
||||
command available, so test it out. When you leave (via the `finish` exit), the command will go away
|
||||
and trying `+setpower` should now give you a command-not-found error. Use `ex me` (as a privileged
|
||||
user) to check so the `Power` [Attribute](../../../Components/Attributes) has been set correctly.
|
||||
user) to check so the `Power` [Attribute](../../../Components/Attributes.md) has been set correctly.
|
||||
|
||||
If things are not working, make sure your typeclasses and commands are free of bugs and that you
|
||||
have entered the paths to the various command sets and commands correctly. Check the logs or command
|
||||
|
|
@ -397,7 +397,7 @@ There are a few ways to define the NPC class. We could in theory create a custom
|
|||
and put a custom NPC-specific cmdset on all NPCs. This cmdset could hold all manipulation commands.
|
||||
Since we expect NPC manipulation to be a common occurrence among the user base however, we will
|
||||
instead put all relevant NPC commands in the default command set and limit eventual access with
|
||||
[Permissions and Locks](../../../Components/Locks#Permissions).
|
||||
[Permissions and Locks](../../../Components/Permissions.md).
|
||||
|
||||
### Creating an NPC with +createNPC
|
||||
|
||||
|
|
@ -454,13 +454,13 @@ class CmdCreateNPC(Command):
|
|||
), exclude=caller)
|
||||
```
|
||||
Here we define a `+createnpc` (`+createNPC` works too) that is callable by everyone *not* having the
|
||||
`nonpcs` "[permission](../../../Components/Locks#Permissions)" (in Evennia, a "permission" can just as well be used to
|
||||
`nonpcs` "[permission](../../../Components/Permissions.md)" (in Evennia, a "permission" can just as well be used to
|
||||
block access, it depends on the lock we define). We create the NPC object in the caller's current
|
||||
location, using our custom `Character` typeclass to do so.
|
||||
|
||||
We set an extra lock condition on the NPC, which we will use to check who may edit the NPC later --
|
||||
we allow the creator to do so, and anyone with the Builders permission (or higher). See
|
||||
[Locks](../../../Components/Locks) for more information about the lock system.
|
||||
[Locks](../../../Components/Locks.md) for more information about the lock system.
|
||||
|
||||
Note that we just give the object default permissions (by not specifying the `permissions` keyword
|
||||
to the `create_object()` call). In some games one might want to give the NPC the same permissions
|
||||
|
|
@ -475,7 +475,7 @@ Since we re-used our custom character typeclass, our new NPC already has a *Powe
|
|||
defaults to 1. How do we change this?
|
||||
|
||||
There are a few ways we can do this. The easiest is to remember that the `power` attribute is just a
|
||||
simple [Attribute](../../../Components/Attributes) stored on the NPC object. So as a Builder or Admin we could set this
|
||||
simple [Attribute](../../../Components/Attributes.md) stored on the NPC object. So as a Builder or Admin we could set this
|
||||
right away with the default `@set` command:
|
||||
|
||||
@set mynpc/power = 6
|
||||
|
|
@ -660,6 +660,6 @@ The simple "Power" game mechanic should be easily expandable to something more f
|
|||
useful, same is true for the combat score principle. The `+attack` could be made to target a
|
||||
specific player (or npc) and automatically compare their relevant attributes to determine a result.
|
||||
|
||||
To continue from here, you can take a look at the [Tutorial World](../Part1/Tutorial-World-Introduction). For
|
||||
more specific ideas, see the [other tutorials and hints](../../Howto-Overview) as well
|
||||
as the [Evennia Component overview](../../../Components/Components-Overview).
|
||||
To continue from here, you can take a look at the [Tutorial World](../Part1/Tutorial-World-Introduction.md). For
|
||||
more specific ideas, see the [other tutorials and hints](../../Howto-Overview.md) as well
|
||||
as the [Evennia Component overview](../../../Components/Components-Overview.md).
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
# Evennia Starting Tutorial (Part 4)
|
||||
|
||||
```sidebar:: Tutorial Parts
|
||||
```{eval-rst}
|
||||
sidebar:: Tutorial Parts
|
||||
|
||||
Part 1: `What we have <../Part1/Starting-Part1.html>`_
|
||||
A tour of Evennia and how to use the tools, including an introduction to Python.
|
||||
Part 2: `What we want <../Part2/Starting-Part2.html>`_
|
||||
Planning our tutorial game and what to think about when planning your own in the future.
|
||||
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
|
||||
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
|
||||
Getting down to the meat of extending Evennia to make our game to make a tech-demo
|
||||
**Part 4: Using what we created**
|
||||
Using the tech-demo and world content to go with our code
|
||||
Part 5: `Showing the world <../Part5/Starting-Part5.html>`_
|
||||
Taking our new game online and let players try it out
|
||||
```
|
||||
```
|
||||
|
||||
We now have the code underpinnings of everything we need. We have also tested the various components
|
||||
We now have the code underpinnings of everything we need. We have also tested the various components
|
||||
and has a simple tech-demo to show it all works together. But there is no real coherence to it at this
|
||||
point - we need to actually make a world.
|
||||
point - we need to actually make a world.
|
||||
In part four we will expand our tech demo into a more full-fledged (if small) game by use of batchcommand
|
||||
and batchcode processors.
|
||||
and batchcode processors.
|
||||
|
||||
_TODO_
|
||||
_TODO_
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
Evennia leverages [Django](https://docs.djangoproject.com) which is a web development framework.
|
||||
Huge professional websites are made in Django and there is extensive documentation (and books) on it
|
||||
. You are encouraged to at least look at the Django basic tutorials. Here we will just give a brief
|
||||
introduction for how things hang together, to get you started.
|
||||
introduction for how things hang together, to get you started.
|
||||
|
||||
We assume you have installed and set up Evennia to run. A webserver and website comes out of the
|
||||
box. You can get to that by entering `http://localhost:4001` in your web browser - you should see a
|
||||
welcome page with some game statistics and a link to the web client. Let us add a new page that you
|
||||
can get to by going to `http://localhost:4001/story`.
|
||||
|
||||
### Create the view
|
||||
## Create the view
|
||||
|
||||
A django "view" is a normal Python function that django calls to render the HTML page you will see
|
||||
in the web browser. Here we will just have it spit back the raw html, but Django can do all sorts of
|
||||
|
|
@ -32,12 +32,12 @@ def storypage(request):
|
|||
This view takes advantage of a shortcut provided to use by Django, _render_. This shortcut gives the
|
||||
template some information from the request, for instance, the game name, and then renders it.
|
||||
|
||||
### The HTML page
|
||||
## The HTML page
|
||||
|
||||
We need to find a place where Evennia (and Django) looks for html files (called *templates* in
|
||||
Django parlance). You can specify such places in your settings (see the `TEMPLATES` variable in
|
||||
`default_settings.py` for more info), but here we'll use an existing one. Go to
|
||||
`mygame/template/overrides/website/` and create a page `story.html` there.
|
||||
`mygame/template/overrides/website/` and create a page `story.html` there.
|
||||
|
||||
This is not a HTML tutorial, so we'll go simple:
|
||||
|
||||
|
|
@ -69,14 +69,14 @@ If you'd rather not take advantage of Evennia's base styles, you can do somethin
|
|||
</html>
|
||||
```
|
||||
|
||||
|
||||
### The URL
|
||||
|
||||
## The URL
|
||||
|
||||
When you enter the address `http://localhost:4001/story` in your web browser, Django will parse that
|
||||
field to figure out which page you want to go to. You tell it which patterns are relevant in the
|
||||
file
|
||||
[mygame/web/urls.py](https://github.com/evennia/evennia/blob/master/evennia/game_template/web/urls.py).
|
||||
Open it now.
|
||||
Open it now.
|
||||
|
||||
Django looks for the variable `urlpatterns` in this file. You want to add your new pattern to the
|
||||
`custom_patterns` list we have prepared - that is then merged with the default `urlpatterns`. Here's
|
||||
|
|
@ -97,4 +97,4 @@ instance. The first argument to `url` is the pattern of the url we want to find
|
|||
a regular expression if you are familiar with those) and then our view function we want to direct
|
||||
to.
|
||||
|
||||
That should be it. Reload Evennia and you should be able to browse to your new story page!
|
||||
That should be it. Reload Evennia and you should be able to browse to your new story page!
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
# Evennia Starting Tutorial (part 5)
|
||||
|
||||
```sidebar:: Tutorial Parts
|
||||
```{eval-rst}
|
||||
.. sidebar:: Tutorial Parts
|
||||
|
||||
Part 1: `What we have <../Part1/Starting-Part1.html>`_
|
||||
A tour of Evennia and how to use the tools, including an introduction to Python.
|
||||
Part 2: `What we want <../Part2/Starting-Part2.html>`_
|
||||
Planning our tutorial game and what to think about when planning your own in the future.
|
||||
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
|
||||
Part 3: `How we get there <../Part3/Starting-Part3.html>`_
|
||||
Getting down to the meat of extending Evennia to make our game
|
||||
Part 4: `Using what we created <../Part4/Starting-Part4.html>`_
|
||||
Building a tech-demo and world content to go with our code
|
||||
|
|
@ -15,7 +16,7 @@
|
|||
```
|
||||
|
||||
You have a working game! In part five we will look at the web-components of Evennia and how to modify them
|
||||
to fit your game. We will also look at hosting your game and if you feel up to it we'll also go through how
|
||||
to bring your game online so you can invite your first players.
|
||||
to fit your game. We will also look at hosting your game and if you feel up to it we'll also go through how
|
||||
to bring your game online so you can invite your first players.
|
||||
|
||||
_TODO_
|
||||
_TODO_
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Evennia uses the [Django](https://www.djangoproject.com/) web framework as the b
|
|||
database configuration and the website it provides. While a full understanding of Django requires
|
||||
reading the Django documentation, we have provided this tutorial to get you running with the basics
|
||||
and how they pertain to Evennia. This text details getting everything set up. The
|
||||
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial) gives a more explicit example of making a
|
||||
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial.md) gives a more explicit example of making a
|
||||
custom web page connected to your game, and you may want to read that after finishing this guide.
|
||||
|
||||
## A Basic Overview
|
||||
|
|
@ -25,7 +25,7 @@ like [CSS](https://en.wikipedia.org/wiki/CSS), [Javascript](https://en.wikipedia
|
|||
and Image files (You may note your mygame/web folder does not have a `static` or `template` folder.
|
||||
This is intended and explained further below). Django applications may also have a `models.py` file
|
||||
for storing information in the database. We will not change any models here, take a look at the
|
||||
[New Models](../../../Concepts/New-Models) page (as well as the [Django docs](https://docs.djangoproject.com/en/1.7/topics/db/models/) on models) if you are interested.
|
||||
[New Models](../../../Concepts/New-Models.md) page (as well as the [Django docs](https://docs.djangoproject.com/en/1.7/topics/db/models/) on models) if you are interested.
|
||||
|
||||
There is also a root `urls.py` that determines the URL structure for the entire project. A starter
|
||||
`urls.py` is included in the default game template, and automatically imports all of Evennia's
|
||||
|
|
@ -104,7 +104,7 @@ run any extra commands to see these changes - reloading the web page in your bro
|
|||
|
||||
To replace the index page's text, we'll need to find the template for it. We'll go into more detail
|
||||
about how to determine which template is used for rendering a page in the
|
||||
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial). For now, you should know that the template we want to change
|
||||
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial.md). For now, you should know that the template we want to change
|
||||
is stored in `evennia/web/website/templates/website/index.html`.
|
||||
|
||||
To replace this template file, you will put your changed template inside the
|
||||
|
|
@ -120,7 +120,7 @@ original file already has all the markup and tags, ready for editing.
|
|||
## Further reading
|
||||
|
||||
For further hints on working with the web presence, you could now continue to the
|
||||
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial) where you learn to make a web page that
|
||||
[Web-based Character view Tutorial](../../Web-Character-View-Tutorial.md) where you learn to make a web page that
|
||||
displays in-game character stats. You can also look at [Django's own
|
||||
tutorial](https://docs.djangoproject.com/en/1.7/intro/tutorial01/) to get more insight in how Django
|
||||
works and what possibilities exist.
|
||||
|
|
@ -5,17 +5,17 @@ This tutorial shows the implementation of an NPC object that responds to charact
|
|||
location. In this example the NPC has the option to respond aggressively or not, but any actions
|
||||
could be triggered this way.
|
||||
|
||||
One could imagine using a [Script](../Components/Scripts) that is constantly checking for newcomers. This would be
|
||||
One could imagine using a [Script](../Components/Scripts.md) that is constantly checking for newcomers. This would be
|
||||
highly inefficient (most of the time its check would fail). Instead we handle this on-demand by
|
||||
using a couple of existing object hooks to inform the NPC that a Character has entered.
|
||||
|
||||
It is assumed that you already know how to create custom room and character typeclasses, please see
|
||||
the [Basic Game tutorial](Starting/Part3/Tutorial-for-basic-MUSH-like-game) if you haven't already done this.
|
||||
the [Basic Game tutorial](Starting/Part3/Tutorial-for-basic-MUSH-like-game.md) if you haven't already done this.
|
||||
|
||||
What we will need is the following:
|
||||
|
||||
- An NPC typeclass that can react when someone enters.
|
||||
- A custom [Room](../Components/Objects#rooms) typeclass that can tell the NPC that someone entered.
|
||||
- A custom [Room](../Components/Objects.md#rooms) typeclass that can tell the NPC that someone entered.
|
||||
- We will also tweak our default `Character` typeclass a little.
|
||||
|
||||
To begin with, we need to create an NPC typeclass. Create a new file inside of your typeclasses
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ their location. In this example the NPC parrots what is said, but any actions co
|
|||
this way.
|
||||
|
||||
It is assumed that you already know how to create custom room and character typeclasses, please see
|
||||
the [Basic Game tutorial](Starting/Part3/Tutorial-for-basic-MUSH-like-game) if you haven't already done this.
|
||||
the [Basic Game tutorial](Starting/Part3/Tutorial-for-basic-MUSH-like-game.md) if you haven't already done this.
|
||||
|
||||
What we will need is simply a new NPC typeclass that can react when someone speaks.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
|
||||
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/How-to-connect-Evennia-to-Twitter) if you
|
||||
account. Please see: [How to connect Evennia to Twitter](../Setup/How-to-connect-Evennia-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
|
||||
|
|
@ -89,7 +89,7 @@ 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) will show you how to add it as a Global script, however, for testing
|
||||
[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
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ and back (assuming we created it in limbo).
|
|||
|
||||
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#exits) to get in and out of the train - Exits are (at least by default) objects
|
||||
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,
|
||||
|
|
@ -58,7 +58,7 @@ regardless of where the Train has moved. Now, one *could* define custom Exit typ
|
|||
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): one for entering the train and
|
||||
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
|
||||
available to whomever is either inside it or in the same room as the train.
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ 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) `CmdSetTrain` so they can be used.
|
||||
[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:
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ 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): we will lock down
|
||||
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
|
||||
|
|
@ -322,7 +322,7 @@ If we wanted full control of the train we could now just add a command to step i
|
|||
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): one script that runs when the train has stopped at
|
||||
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
|
||||
care of the driving.
|
||||
|
||||
|
|
@ -416,7 +416,7 @@ enter/exit commands check so the train is not moving before allowing the caller
|
|||
* 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#exits) only traversable by trains. The train will follow the
|
||||
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!
|
||||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
This tutorial aims at dispelling confusions regarding the use of color tags within Evennia.
|
||||
|
||||
Correct understanding of this topic requires having read the [TextTags](../Concepts/TextTags) page and learned
|
||||
Correct understanding of this topic requires having read the [TextTags](../Concepts/TextTags.md) page and learned
|
||||
Evennia's color tags. Here we'll explain by examples the reasons behind the unexpected (or
|
||||
apparently incoherent) behaviors of some color tags, as mentioned _en passant_ in the
|
||||
[TextTags](../Concepts/TextTags) page.
|
||||
[TextTags](../Concepts/TextTags.md) page.
|
||||
|
||||
|
||||
All you'll need for this tutorial is access to a running instance of Evennia via a color-enabled
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ individually track the time, they instead subscribe to be called by a global tic
|
|||
keeping. Not only does this centralize and organize much of the code in one place, it also has less
|
||||
computing overhead.
|
||||
|
||||
Evennia offers the [TickerHandler](../Components/TickerHandler) specifically for using the subscription model. We
|
||||
Evennia offers the [TickerHandler](../Components/TickerHandler.md) specifically for using the 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.
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ needed.
|
|||
|
||||
## Pictures
|
||||
|
||||
Here are some screenshots of the simple app we will be making.
|
||||
Here are some screenshots of the simple app we will be making.
|
||||
|
||||
Index page, with no character application yet done:
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ and *templates* (how the web page should be structured).
|
|||
|
||||
Models are created in `mygame/web/chargen/models.py`.
|
||||
|
||||
A [Django database model](../Concepts/New-Models) is a Python class that describes the database storage of the
|
||||
A [Django database model](../Concepts/New-Models.md) is a Python class that describes the database storage of the
|
||||
data you want to manage. Any data you choose to store is stored in the same database as the game and
|
||||
you have access to all the game's objects here.
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ AccountID from the AccountDB object.
|
|||
> Note: In a full-fledged game, you’d likely want them to be able to select races, skills,
|
||||
attributes and so on.
|
||||
|
||||
Our `models.py` file should look something like this:
|
||||
Our `models.py` file should look something like this:
|
||||
|
||||
```python
|
||||
# in mygame/web/chargen/models.py
|
||||
|
|
@ -139,13 +139,13 @@ specific web page. We will use three views and three pages here:
|
|||
`http://yoursite.com/chargen`.
|
||||
* The detail display sheet (manages `detail.html`). A page that passively displays the stats of a
|
||||
given Character.
|
||||
* Character creation sheet (manages `create.html`). This is the main form with fields to fill in.
|
||||
* Character creation sheet (manages `create.html`). This is the main form with fields to fill in.
|
||||
|
||||
### *Index* view
|
||||
|
||||
Let’s get started with the index first.
|
||||
|
||||
We’ll want characters to be able to see their created characters so let’s
|
||||
We’ll want characters to be able to see their created characters so let’s
|
||||
|
||||
```python
|
||||
# file mygame/web/chargen.views.py
|
||||
|
|
@ -182,7 +182,7 @@ def detail(request, app_id):
|
|||
background = app.background
|
||||
submitted = app.submitted
|
||||
p_id = request.user.id
|
||||
context = {'name': name, 'background': background,
|
||||
context = {'name': name, 'background': background,
|
||||
'p_id': p_id, 'submitted': submitted}
|
||||
return render(request, 'chargen/detail.html', context)
|
||||
```
|
||||
|
|
@ -206,7 +206,7 @@ class AppForm(forms.Form):
|
|||
background = forms.CharField(label='Background')
|
||||
```
|
||||
|
||||
Now we make use of this form in our view.
|
||||
Now we make use of this form in our view.
|
||||
|
||||
```python
|
||||
# file mygame/web/chargen/views.py
|
||||
|
|
@ -230,8 +230,8 @@ def creating(request):
|
|||
submitted = True
|
||||
if 'save' in request.POST:
|
||||
submitted = False
|
||||
app = CharApp(char_name=name, background=background,
|
||||
date_applied=applied_date, account_id=user.id,
|
||||
app = CharApp(char_name=name, background=background,
|
||||
date_applied=applied_date, account_id=user.id,
|
||||
submitted=submitted)
|
||||
app.save()
|
||||
if submitted:
|
||||
|
|
@ -239,9 +239,9 @@ def creating(request):
|
|||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
home = ObjectDB.objects.get_id(settings.GUEST_HOME)
|
||||
# turn the permissionhandler to a string
|
||||
perms = str(user.permissions)
|
||||
perms = str(user.permissions)
|
||||
# create the character
|
||||
char = create.create_object(typeclass=typeclass, key=name,
|
||||
char = create.create_object(typeclass=typeclass, key=name,
|
||||
home=home, permissions=perms)
|
||||
user.db._playable_characters.append(char)
|
||||
# add the right locks for the character so the account can
|
||||
|
|
@ -266,9 +266,9 @@ create_object function to properly process the permissions.
|
|||
|
||||
Most importantly, the following attributes must be set on the created character object:
|
||||
|
||||
* Evennia [permissions](../Components/Locks#permissions) (copied from the `AccountDB`).
|
||||
* The right `puppet` [locks](../Components/Locks) so the Account can actually play as this Character later.
|
||||
* The relevant Character [typeclass](../Components/Typeclasses)
|
||||
* Evennia [permissions](../Components/Permissions.md) (copied from the `AccountDB`).
|
||||
* The right `puppet` [locks](../Components/Locks.md) so the Account can actually play as this Character later.
|
||||
* The relevant Character [typeclass](../Components/Typeclasses.md)
|
||||
* Character name (key)
|
||||
* The Character's home room location (`#2` by default)
|
||||
|
||||
|
|
@ -281,7 +281,7 @@ After all of this, our `views.py` file should look like something like this:
|
|||
|
||||
```python
|
||||
# file mygame/web/chargen/views.py
|
||||
|
||||
|
||||
from django.shortcuts import render
|
||||
from web.chargen.models import CharApp
|
||||
from web.chargen.forms import AppForm
|
||||
|
|
@ -305,7 +305,7 @@ def detail(request, app_id):
|
|||
background = app.background
|
||||
submitted = app.submitted
|
||||
p_id = request.user.id
|
||||
context = {'name': name, 'background': background,
|
||||
context = {'name': name, 'background': background,
|
||||
'p_id': p_id, 'submitted': submitted}
|
||||
return render(request, 'chargen/detail.html', context)
|
||||
|
||||
|
|
@ -320,8 +320,8 @@ def creating(request):
|
|||
submitted = True
|
||||
if 'save' in request.POST:
|
||||
submitted = False
|
||||
app = CharApp(char_name=name, background=background,
|
||||
date_applied=applied_date, account_id=user.id,
|
||||
app = CharApp(char_name=name, background=background,
|
||||
date_applied=applied_date, account_id=user.id,
|
||||
submitted=submitted)
|
||||
app.save()
|
||||
if submitted:
|
||||
|
|
@ -329,9 +329,9 @@ def creating(request):
|
|||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
home = ObjectDB.objects.get_id(settings.GUEST_HOME)
|
||||
# turn the permissionhandler to a string
|
||||
perms = str(user.permissions)
|
||||
perms = str(user.permissions)
|
||||
# create the character
|
||||
char = create.create_object(typeclass=typeclass, key=name,
|
||||
char = create.create_object(typeclass=typeclass, key=name,
|
||||
home=home, permissions=perms)
|
||||
user.db._playable_characters.append(char)
|
||||
# add the right locks for the character so the account can
|
||||
|
|
@ -507,7 +507,7 @@ up on documentation elsewhere on the web for GET vs. POST.
|
|||
{% endblock %}
|
||||
```
|
||||
|
||||
### Templates - Checkpoint:
|
||||
### Templates - Checkpoint:
|
||||
|
||||
* Create a `index.html`, `detail.html` and `create.html` template in your
|
||||
`mygame/web/chargen/templates/chargen` directory
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ wasn't generated for you):
|
|||
# URL patterns for the character app
|
||||
|
||||
from django.conf.urls import url
|
||||
from web.character.views import sheet
|
||||
from web.character.views import sheet
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^sheet/(?P<object_id>\d+)/$', sheet, name="sheet")
|
||||
|
|
@ -222,8 +222,8 @@ As an optional final step, you can also change your character typeclass to have
|
|||
```
|
||||
Doing so will give you a 'view on site' button in the top right of the Django Admin Objects
|
||||
changepage that links to your new character sheet, and allow you to get the link to a character's
|
||||
page by using {{ object.get_absolute_url }} in any template where you have a given object.
|
||||
page by using `{{ object.get_absolute_url }}` in any template where you have a given object.
|
||||
|
||||
*Now that you've made a basic page and app with Django, you may want to read the full Django
|
||||
tutorial to get a better idea of what it can do. [You can find Django's tutorial
|
||||
here](https://docs.djangoproject.com/en/1.8/intro/tutorial01/).*
|
||||
here](https://docs.djangoproject.com/en/1.8/intro/tutorial01/).*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue