mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Further work on the python tut
This commit is contained in:
parent
221caf5187
commit
131a9f89aa
6 changed files with 325 additions and 18 deletions
|
|
@ -1,120 +0,0 @@
|
|||
# Execute Python Code
|
||||
|
||||
|
||||
The `@py` command supplied with the default command set of Evennia allows you to execute Python
|
||||
commands directly from inside the game. An alias to `@py` is simply "`!`". *Access to the `@py`
|
||||
command should be severely restricted*. This is no joke - being able to execute arbitrary Python
|
||||
code on the server is not something you should entrust to just anybody.
|
||||
|
||||
@py 1+2
|
||||
<<< 3
|
||||
|
||||
## Available variables
|
||||
|
||||
A few local variables are made available when running `@py`. These offer entry into the running
|
||||
system.
|
||||
|
||||
- **self** / **me** - the calling object (i.e. you)
|
||||
- **here** - the current caller's location
|
||||
- **obj** - a dummy [Object](../../Component/Objects) instance
|
||||
- **evennia** - Evennia's [flat API](../../Evennia-API) - through this you can access all of Evennia.
|
||||
|
||||
For accessing other objects in the same room you need to use `self.search(name)`. For objects in
|
||||
other locations, use one of the `evennia.search_*` methods. See [below](Execute-Python-Code#finding-
|
||||
objects).
|
||||
|
||||
## Returning output
|
||||
|
||||
This is an example where we import and test one of Evennia's utilities found in
|
||||
`src/utils/utils.py`, but also accessible through `ev.utils`:
|
||||
|
||||
@py from ev import utils; utils.time_format(33333)
|
||||
<<< Done.
|
||||
|
||||
Note that we didn't get any return value, all we where told is that the code finished executing
|
||||
without error. This is often the case in more complex pieces of code which has no single obvious
|
||||
return value. To see the output from the `time_format()` function we need to tell the system to
|
||||
echo it to us explicitly with `self.msg()`.
|
||||
|
||||
@py from ev import utils; self.msg(str(utils.time_format(33333)))
|
||||
09:15
|
||||
<<< Done.
|
||||
|
||||
> Warning: When using the `msg` function wrap our argument in `str()` to convert it into a string
|
||||
above. This is not strictly necessary for most types of data (Evennia will usually convert to a
|
||||
string behind the scenes for you). But for *lists* and *tuples* you will be confused by the output
|
||||
if you don't wrap them in `str()`: only the first item of the iterable will be returned. This is
|
||||
because doing `msg(text)` is actually just a convenience shortcut; the full argument that `msg`
|
||||
accepts is something called an *outputfunc* on the form `(cmdname, (args), {kwargs})` (see [the
|
||||
message path](Messagepath) for more info). Sending a list/tuple confuses Evennia to think you are
|
||||
sending such a structure. Converting it to a string however makes it clear it should just be
|
||||
displayed as-is.
|
||||
|
||||
If you were to use Python's standard `print`, you will see the result in your current `stdout` (your
|
||||
terminal by default, otherwise your log file).
|
||||
|
||||
## Finding objects
|
||||
|
||||
A common use for `@py` is to explore objects in the database, for debugging and performing specific
|
||||
operations that are not covered by a particular command.
|
||||
|
||||
Locating an object is best done using `self.search()`:
|
||||
|
||||
@py self.search("red_ball")
|
||||
<<< Ball
|
||||
|
||||
@py self.search("red_ball").db.color = "red"
|
||||
<<< Done.
|
||||
|
||||
@py self.search("red_ball").db.color
|
||||
<<< red
|
||||
|
||||
`self.search()` is by far the most used case, but you can also search other database tables for
|
||||
other Evennia entities like scripts or configuration entities. To do this you can use the generic
|
||||
search entries found in `ev.search_*`.
|
||||
|
||||
@py evennia.search_script("sys_game_time")
|
||||
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
|
||||
|
||||
(Note that since this becomes a simple statement, we don't have to wrap it in `self.msg()` to get
|
||||
the output). You can also use the database model managers directly (accessible through the `objects`
|
||||
properties of database models or as `evennia.managers.*`). This is a bit more flexible since it
|
||||
gives you access to the full range of database search methods defined in each manager.
|
||||
|
||||
@py evennia.managers.scripts.script_search("sys_game_time")
|
||||
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
|
||||
|
||||
The managers are useful for all sorts of database studies.
|
||||
|
||||
@py ev.managers.configvalues.all()
|
||||
<<< [<ConfigValue: default_home]>, <ConfigValue:site_name>, ...]
|
||||
|
||||
## Testing code outside the game
|
||||
|
||||
`@py` has the advantage of operating inside a running server (sharing the same process), where you
|
||||
can test things in real time. Much of this *can* be done from the outside too though.
|
||||
|
||||
In a terminal, cd to the top of your game directory (this bit is important since we need access to
|
||||
your config file) and run
|
||||
|
||||
evennia shell
|
||||
|
||||
Your default Python interpreter will start up, configured to be able to work with and import all
|
||||
modules of your Evennia installation. From here you can explore the database and test-run individual
|
||||
modules as desired.
|
||||
|
||||
It's recommended that you get a more fully featured Python interpreter like
|
||||
[iPython](http://ipython.scipy.org/moin/). If you use a virtual environment, you can just get it
|
||||
with `pip install ipython`. IPython allows you to better work over several lines, and also has a lot
|
||||
of other editing features, such as tab-completion and `__doc__`-string reading.
|
||||
|
||||
$ evennia shell
|
||||
|
||||
IPython 0.10 -- An enhanced Interactive Python
|
||||
...
|
||||
|
||||
In [1]: import evennia
|
||||
In [2]: evennia.managers.objects.all()
|
||||
Out[3]: [<ObjectDB: Harry>, <ObjectDB: Limbo>, ...]
|
||||
|
||||
See the page about the [Evennia-API](../../Evennia-API) for more things to explore.
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
# Overview of your new Game Dir
|
||||
|
||||
[prev lesson](Python-basic-introduction) | [next lesson]()
|
||||
[prev lesson](Python-basic-introduction) | [next lesson](Python-classes-and-objects)
|
||||
|
||||
Next we will take a little detour to look at the _Tutorial World_. This is a little solo adventure
|
||||
that comes with Evennia, a showcase for some of the things that are possible.
|
||||
|
||||
Now we have 'run the game' a bit and started with our forays into Python from inside Evennia.
|
||||
It is time to start to look at how things look 'outside of the game'. Let's do a tour of your game-dir
|
||||
|
|
@ -205,4 +208,4 @@ people change and re-structure this in various ways to better fit their ideas.
|
|||
tell that Two goblins, while both of the class 'Goblin' (so they follow the same code logic), should have different
|
||||
equipment, stats and looks.
|
||||
|
||||
[prev lesson](Python-basic-introduction) | [next lesson]()
|
||||
[prev lesson](Python-basic-introduction) | [next lesson](Python-classes-and-objects)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,313 @@
|
|||
# Python basic tutorial part two
|
||||
# Continuing on with Python and Evennia
|
||||
|
||||
[In the first part](Part1/Python-basic-introduction) of this Python-for-Evennia basic tutorial we learned
|
||||
[prev lesson](Gamedir-Overview) | [next lesson]()
|
||||
|
||||
We have now learned how to run some simple Python code from inside (and outside) your game server.
|
||||
We have also taken a look at what our game dir looks and what is where. Now we'll start to use it.
|
||||
|
||||
## Importing
|
||||
|
||||
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,
|
||||
and it will import and use it (often instead of its defaults).
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
mygame/
|
||||
world/
|
||||
test.py <- inside this file is a function hello_world
|
||||
|
||||
```
|
||||
If you followed earlier tutorial lessons, the `mygame/world/test.py` file should look like this (if
|
||||
not, make it so):
|
||||
|
||||
```python
|
||||
def hello_world(who):
|
||||
who.msg("Hello World!")
|
||||
```
|
||||
|
||||
```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
|
||||
for python resources inside `mygame/` so this should never be specified. Hence
|
||||
|
||||
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:
|
||||
|
||||
from world.test import hello_world
|
||||
|
||||
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:
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
> 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
|
||||
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]
|
||||
|
||||
You now only need to import once to use the imported function over and over.
|
||||
|
||||
> from world.test import hello_world
|
||||
> hello_world()
|
||||
Hello World!
|
||||
> hello_world()
|
||||
Hello World!
|
||||
> hello_world()
|
||||
Hello World!
|
||||
> quit()
|
||||
Closing the Python console.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
```python
|
||||
"""
|
||||
module docstring
|
||||
"""
|
||||
from evennia import DefaultObject
|
||||
|
||||
class Object(DefaultObject):
|
||||
"""
|
||||
class docstring
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
```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
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
To understand what we are looking at, we need to explain what a 'class', an 'object' and an 'instance' is.
|
||||
```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 and instances
|
||||
|
||||
A 'class' can be seen as a 'template' for a 'type' of object. The class describes the basic functionality
|
||||
of everyone of that class. For example, we could have a class `Mobile` which has resources for moving itself
|
||||
from room to room.
|
||||
|
||||
Open a new file `mygame/typeclasses/mymobile.py`. Add the following simple class:
|
||||
|
||||
```python
|
||||
|
||||
class Mobile:
|
||||
|
||||
key = "Monster"
|
||||
|
||||
def move_around(self):
|
||||
print(f"{self.key} is moving!")
|
||||
|
||||
```
|
||||
|
||||
Above we have defined a `Mobile` 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.
|
||||
|
||||
A class is just a template. Before it can be used, we must create an _instance_ of the class. If
|
||||
`Mobile` 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 = Mobile()
|
||||
|
||||
Let's try it in-game (we use multi-line mode, it's easier)
|
||||
|
||||
> py
|
||||
> from typeclasses.mymobile import Mobile
|
||||
> fluffy = Mobile()
|
||||
> fluffy.move_around()
|
||||
Monster is moving!
|
||||
|
||||
We created an _instance_ of `Mobile`, 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).
|
||||
|
||||
Let's create the sibling of Fluffy, Cuddly:
|
||||
|
||||
> cuddly = Mobile()
|
||||
> cuddly.move_around()
|
||||
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
|
||||
create, they will all show the same printout since `key` is always fixed as "Monster".
|
||||
|
||||
Let's make the class a little more flexible:
|
||||
|
||||
```python
|
||||
|
||||
class Mobile:
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = key
|
||||
|
||||
def move_around(self):
|
||||
print(f"{self.key} is moving!")
|
||||
|
||||
```
|
||||
|
||||
The `__init__` is a special method that Python recognizes. If given, this handles extra arguments
|
||||
when you instantiate a new Mobile. 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:
|
||||
|
||||
> quit()
|
||||
Python Console is closing.
|
||||
> reload
|
||||
|
||||
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
|
||||
this, but actual code is normally written externally, in python modules.
|
||||
```
|
||||
|
||||
$ evennia reload (or restart)
|
||||
|
||||
Either way you'll need to go into `py` again:
|
||||
|
||||
> py
|
||||
> from typeclasses.mymobile import Mobile
|
||||
fluffy = Mobile("Fluffy")
|
||||
fluffy.move_around()
|
||||
Fluffy is moving!
|
||||
|
||||
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?
|
||||
|
||||
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 mobile_move_around(key):
|
||||
print(f"{key} is moving!")
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
> 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:
|
||||
|
||||
- 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
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[In the first part](Python-basic-introduction) of this Python-for-Evennia basic tutorial we learned
|
||||
how to run some simple Python code from inside the game. We also made our first new *module*
|
||||
containing a *function* that we called. Now we're going to start exploring the very important
|
||||
containing a *function* that we called. Now we're going to start exploring the very important
|
||||
subject of *objects*.
|
||||
|
||||
**Contents:**
|
||||
- [On the subject of objects](Python-basic-tutorial-part-two#on-the-subject-of-objects)
|
||||
- [On the subject of objects](Python-basic-tutorial-part-two#on-the-subject-of-objects)
|
||||
- [Exploring the Evennia library](Python-basic-tutorial-part-two#exploring-the-evennia-library)
|
||||
- [Tweaking our Character class](Python-basic-tutorial-part-two#tweaking-our-character-class)
|
||||
- [The Evennia shell](Python-basic-tutorial-part-two#the-evennia-shell)
|
||||
|
|
@ -224,7 +525,7 @@ is the same thing, just a little easier to remember.
|
|||
|
||||
> To access the shortcuts of the flat API you *must* use `from evennia import
|
||||
> ...`. Using something like `import evennia.DefaultCharacter` will not work.
|
||||
> See [more about the Flat API here](../../Evennia-API).
|
||||
> See [more about the Flat API here](../../../Evennia-API).
|
||||
|
||||
|
||||
### Tweaking our Character class
|
||||
|
|
@ -283,8 +584,8 @@ brief summary of the methods we find in `DefaultCharacter` (follow in the code t
|
|||
roughly where things happen)::
|
||||
|
||||
- `basetype_setup` is called by Evennia only once, when a Character is first created. In the
|
||||
`DefaultCharacter` class it sets some particular [Locks](../../Component/Locks) so that people can't pick up and
|
||||
puppet Characters just like that. It also adds the [Character Cmdset](../../Component/Command-Sets) so that
|
||||
`DefaultCharacter` class it sets some particular [Locks](../../../Component/Locks) so that people can't pick up and
|
||||
puppet Characters just like that. It also adds the [Character Cmdset](../../../Component/Command-Sets) so that
|
||||
Characters always can accept command-input (this should usually not be modified - the normal hook to
|
||||
override is `at_object_creation`, which is called after `basetype_setup` (it's in the parent)).
|
||||
- `at_after_move` makes it so that every time the Character moves, the `look` command is
|
||||
|
|
@ -484,7 +785,7 @@ convenient for quickly exploring code without having to go digging through the f
|
|||
|
||||
This should give you a running start using Python with Evennia. If you are completely new to
|
||||
programming or Python you might want to look at a more formal Python tutorial. You can find links
|
||||
and resources [on our link page](../../Links).
|
||||
and resources [on our link page](../../../Links).
|
||||
|
||||
We have touched upon many of the concepts here but to use Evennia and to be able to follow along in
|
||||
the code, you will need basic understanding of Python
|
||||
|
|
@ -502,4 +803,6 @@ programming](http://www.tutorialspoint.com/python/python_classes_objects.htm) an
|
|||
Once you have familiarized yourself, or if you prefer to pick Python up as you go, continue to one
|
||||
of the beginning-level [Evennia tutorials](Tutorials) to gradually build up your understanding.
|
||||
|
||||
Good luck!
|
||||
Good luck!
|
||||
|
||||
[prev lesson](Gamedir-Overview) | [next lesson]()
|
||||
|
|
@ -25,8 +25,9 @@ own first little game in Evennia. Let's get started!
|
|||
1. [Building stuff](Part1/Building-Quickstart)
|
||||
1. [The Tutorial World](Part1/Tutorial-World-Introduction)
|
||||
1. [Python basics](Part1/Python-basic-introduction)
|
||||
1. [Game dir overview](Part1/Gamedir-Overview)
|
||||
1. [Python classes](Python-basic-tutorial-part-two)
|
||||
1. [Running Python in- and outside the game](Execute-Python-Code)
|
||||
1. [Running Python in- and outside the game](../../Coding/Execute-Python-Code)
|
||||
1. [Understanding errors](Understanding-Errors)
|
||||
1. [Searching for things](Tutorial-Searching-For-Objects)
|
||||
1. [A walkthrough of the API](Walkthrough-of-API)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue