From e7f4967201bf5ef589f75b7cd308d9c7db89e049 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 23 Jun 2020 00:13:21 +0200 Subject: [PATCH] More updates to the starting tutorial --- docs/source/Contributing-Docs.md | 28 +- docs/source/Contributing.md | 18 +- docs/source/How-To-Get-And-Give-Help.md | 22 +- .../Howto/Starting/Building-Quickstart.md | 17 +- .../Howto/Starting/Coding-Introduction.md | 2 +- .../Starting/Python-basic-introduction.md | 576 ++++++++++++++---- docs/source/Setup/Client-Support-Grid.md | 44 +- docs/source/Setup/Online-Setup.md | 20 +- docs/source/conf.py | 16 +- evennia/commands/default/system.py | 4 +- 10 files changed, 553 insertions(+), 194 deletions(-) diff --git a/docs/source/Contributing-Docs.md b/docs/source/Contributing-Docs.md index 9686923758..85926052cd 100644 --- a/docs/source/Contributing-Docs.md +++ b/docs/source/Contributing-Docs.md @@ -298,7 +298,7 @@ This is a [clickable link][mylink]. This is [another link][1]. ... -[mylink]: http://... +[mylink](http://...) [1]: My-Document ``` @@ -662,19 +662,19 @@ to understand our friendly Google-style docstrings used in classes and functions -[sphinx]: https://www.sphinx-doc.org/en/master/ -[recommonmark]: https://recommonmark.readthedocs.io/en/latest/index.html -[commonmark]: https://spec.commonmark.org/current/ -[commonmark-help]: https://commonmark.org/help/ -[sphinx-autodoc]: http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc -[sphinx-napoleon]: http://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html +[sphinx](https://www.sphinx-doc.org/en/master/) +[recommonmark](https://recommonmark.readthedocs.io/en/latest/index.html) +[commonmark](https://spec.commonmark.org/current/) +[commonmark-help](https://commonmark.org/help/) +[sphinx-autodoc](http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc) +[sphinx-napoleon](http://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html) [getting-started]: Setup/Getting-Started [contributing]: Contributing -[ReST]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html -[ReST-tables]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#tables -[ReST-directives]: https://www.sphinx-doc.org/en/master/usage/restruturedtext/directives.html -[Windows-WSL]: https://docs.microsoft.com/en-us/windows/wsl/install-win10 +[ReST](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) +[ReST-tables](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#tables) +[ReST-directives](https://www.sphinx-doc.org/en/master/usage/restruturedtext/directives.html) +[Windows-WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) [linkdemo]: #Links -[retext]: https://github.com/retext-project/retext -[grip]: https://github.com/joeyespo/grip -[pycharm]: https://www.jetbrains.com/pycharm/ +[retext](https://github.com/retext-project/retext) +[grip](https://github.com/joeyespo/grip) +[pycharm](https://www.jetbrains.com/pycharm/) diff --git a/docs/source/Contributing.md b/docs/source/Contributing.md index 17e6342b3c..7b6737b1f3 100644 --- a/docs/source/Contributing.md +++ b/docs/source/Contributing.md @@ -105,14 +105,14 @@ beyond our manpower. However, if your code were to *not* be accepted for merger will instead add a link to your online repository so people can still find and use your work if they want. -[ohloh]: http://www.ohloh.net/p/evennia -[patron]: https://www.patreon.com/griatch -[donate]: https://www.paypal.com/en/cgi-bin/webscr?cmd=_flow&SESSION=TWy_epDPSWqNr4UJCOtVWxl- +[ohloh](http://www.ohloh.net/p/evennia) +[patron](https://www.patreon.com/griatch) +[donate](https://www.paypal.com/en/cgi-bin/webscr?cmd=_flow&SESSION=TWy_epDPSWqNr4UJCOtVWxl-) pO1X1jbKiv_- UBBFWIuVDEZxC0M_2pM6ywO&dispatch=5885d80a13c0db1f8e263663d3faee8d66f31424b43e9a70645c907a6cbd8fb4 -[forking]: https://github.com/evennia/evennia/wiki/Version-Control#wiki-forking-from-evennia -[pullrequest]: https://github.com/evennia/evennia/pulls -[issues]: https://github.com/evennia/evennia/issues -[patch]: https://secure.wikimedia.org/wikipedia/en/wiki/Patch_%28computing%29 -[codestyle]: https://github.com/evennia/evennia/blob/master/CODING_STYLE.md -[tutorials]: https://github.com/evennia/evennia/wiki/Tutorials \ No newline at end of file +[forking](https://github.com/evennia/evennia/wiki/Version-Control#wiki-forking-from-evennia) +[pullrequest](https://github.com/evennia/evennia/pulls) +[issues](https://github.com/evennia/evennia/issues) +[patch](https://secure.wikimedia.org/wikipedia/en/wiki/Patch_%28computing%29 ) +[codestyle](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md) +[tutorials](https://github.com/evennia/evennia/wiki/Tutorials) \ No newline at end of file diff --git a/docs/source/How-To-Get-And-Give-Help.md b/docs/source/How-To-Get-And-Give-Help.md index defb706bbb..321c7fadf0 100644 --- a/docs/source/How-To-Get-And-Give-Help.md +++ b/docs/source/How-To-Get-And-Give-Help.md @@ -54,15 +54,15 @@ appreciate the work done with the server! You can also encourage the community t issues by putting up a monetary [bounty][bountysource] on it. -[form]: https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0 -[group]: http://groups.google.com/group/evennia/ -[issues]: https://github.com/evennia/evennia/issues -[issues-master]: https://github.com/evennia/evennia/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Abug%20label%3Amaster-branch -[chat]: http://webchat.freenode.net/?channels=evennia -[paypal]: https://www.paypal.com/se/cgi-bin/webscr?cmd=_flow&SESSION=Z-VlOvfGjYq2qvCDOUGpb6C8Due7skT0qOklQEy5EbaD1f0eyEQaYlmCc8O&dispatch=5885d80a13c0db1f8e263663d3faee8d64ad11bbf4d2a5a1a0d303a50933f9b2 -[donate-img]: http://images-focus-opensocial.googleusercontent.com/gadgets/proxy?url=https://www.paypalobjects.com/en%255fUS/SE/i/btn/btn%255fdonateCC%255fLG.gif&container=focus&gadget=a&rewriteMime=image/* -[patreon]: https://www.patreon.com/griatch -[patreon-img]: http://www.evennia.com/_/rsrc/1424724909023/home/evennia_patreon_100x100.png -[issues-bounties]: https://github.com/evennia/evennia/labels/bounty -[bountysource]: https://www.bountysource.com/teams/evennia +[form](https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0) +[group](http://groups.google.com/group/evennia/) +[issues](https://github.com/evennia/evennia/issues) +[issues-master](https://github.com/evennia/evennia/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Abug%20label%3Amaster-branch) +[chat](http://webchat.freenode.net/?channels=evennia) +[paypal](https://www.paypal.com/se/cgi-bin/webscr?cmd=_flow&SESSION=Z-VlOvfGjYq2qvCDOUGpb6C8Due7skT0qOklQEy5EbaD1f0eyEQaYlmCc8O&dispatch=5885d80a13c0db1f8e263663d3faee8d64ad11bbf4d2a5a1a0d303a50933f9b2) +[donate-img](http://images-focus-opensocial.googleusercontent.com/gadgets/proxy?url=https://www.paypalobjects.com/en%255fUS/SE/i/btn/btn%255fdonateCC%255fLG.gif&container=focus&gadget=a&rewriteMime=image/*) +[patreon](https://www.patreon.com/griatch) +[patreon-img](http://www.evennia.com/_/rsrc/1424724909023/home/evennia_patreon_100x100.png) +[issues-bounties](https://github.com/evennia/evennia/labels/bounty) +[bountysource](https://www.bountysource.com/teams/evennia) diff --git a/docs/source/Howto/Starting/Building-Quickstart.md b/docs/source/Howto/Starting/Building-Quickstart.md index 15b699f69b..573e849826 100644 --- a/docs/source/Howto/Starting/Building-Quickstart.md +++ b/docs/source/Howto/Starting/Building-Quickstart.md @@ -83,6 +83,15 @@ 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 + + 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. + +``` + + We now renamed the box to _very large box_ (and this is what we will see when looking at it), but we will also recognize it by any of the other names we give - like _crate_ or simply _box_ as before. We could have given these aliases directly after the name in the `create` command, this is true for @@ -297,13 +306,11 @@ the history of your game world: sethelp/add History = At the dawn of time ... -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. - +You will now find your new `History` entry in the `help` list and read your help-text with `help History`. ## Adding a World -After this brief introduction to building 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. +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. [prev lesson](Starting-Part1) | [next lesson](Tutorial-World-Introduction) diff --git a/docs/source/Howto/Starting/Coding-Introduction.md b/docs/source/Howto/Starting/Coding-Introduction.md index 7a71ac39ba..f33a4b4ceb 100644 --- a/docs/source/Howto/Starting/Coding-Introduction.md +++ b/docs/source/Howto/Starting/Coding-Introduction.md @@ -99,4 +99,4 @@ chat](http://webchat.freenode.net/?channels=evennia) are also there for you. And finally, of course, have fun! [feature-request]: (https://github.com/evennia/evennia/issues/new?title=Feature+Request%3a+%3Cdescriptive+title+here%3E&body=%23%23%23%23+Description+of+the+suggested+feature+and+how+it+is+supposed+to+work+for+the+admin%2fend+user%3a%0D%0A%0D%0A%0D%0A%23%23%23%23+A+list+of+arguments+for+why+you+think+this+new+feature+should+be+included+in+Evennia%3a%0D%0A%0D%0A1.%0D%0A2.%0D%0A%0D%0A%23%23%23%23+Extra+information%2c+such+as+requirements+or+ideas+on+implementation%3a%0D%0A%0D%0A -[bug]: https://github.com/evennia/evennia/issues/new?title=Bug%3a+%3Cdescriptive+title+here%3E&body=%23%23%23%23+Steps+to+reproduce+the+issue%3a%0D%0A%0D%0A1.+%0D%0A2.+%0D%0A3.+%0D%0A%0D%0A%23%23%23%23+What+I+expect+to+see+and+what+I+actually+see+%28tracebacks%2c+error+messages+etc%29%3a%0D%0A%0D%0A%0D%0A%0D%0A%23%23%23%23+Extra+information%2c+such+as+Evennia+revision%2frepo%2fbranch%2c+operating+system+and+ideas+for+how+to+solve%3a%0D%0A%0D%0A \ No newline at end of file +[bug](https://github.com/evennia/evennia/issues/new?title=Bug%3a+%3Cdescriptive+title+here%3E&body=%23%23%23%23+Steps+to+reproduce+the+issue%3a%0D%0A%0D%0A1.+%0D%0A2.+%0D%0A3.+%0D%0A%0D%0A%23%23%23%23+What+I+expect+to+see+and+what+I+actually+see+%28tracebacks%2c+error+messages+etc%29%3a%0D%0A%0D%0A%0D%0A%0D%0A%23%23%23%23+Extra+information%2c+such+as+Evennia+revision%2frepo%2fbranch%2c+operating+system+and+ideas+for+how+to+solve%3a%0D%0A%0D%0A) \ No newline at end of file diff --git a/docs/source/Howto/Starting/Python-basic-introduction.md b/docs/source/Howto/Starting/Python-basic-introduction.md index 022f22ae0b..fe00aaf866 100644 --- a/docs/source/Howto/Starting/Python-basic-introduction.md +++ b/docs/source/Howto/Starting/Python-basic-introduction.md @@ -29,7 +29,7 @@ game. This is useful for quick testing. From the game's input line, enter the fo ```sidebar:: Command input - The lines with a `>` indicate input to enter in-game, while the lines below are the + The line with `>` indicates input to enter in-game, while the lines below are the expected return from that input. ``` @@ -41,51 +41,140 @@ You will see To understand what is going on: some extra info: The `print(...)` *function* is the basic, in-built way to output text in Python. We are sending "Hello World" as an _argument_ to this function. The quotes `"..."` mean that you are inputting a *string* (i.e. text). You could also have used single-quotes `'...'`, -Python accepts both. +Python accepts both. A third variant is triple-quotes (`"""..."""` or `'''...'''`, which work across multiple +lines and are common for larger text-blocks. The way we use the `py` command right now only supports +single-line input however. -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 -to an actual player, `print` won't do, because it doesn't know _who_ to send to. Try this: +### Making some text 'graphics' - > py me.msg("Hello world!") - Hello world! +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 +text and get text back. As we saw above, a piece of text is called a _string_ in Python and is enclosed in +either single- or double-quotes. -This looks the same as the `print` result, but we are now actually messaging a specific *object*, -`me`. The `me` is a shortcut to 'us', the one running the `py` command. It is not some special -Python thing, but something Evennia just makes available in the `py` command for convenience -(`self` is an alias). +Strings can be added together: -The `me` is an example of an *Object instance*. Objects are fundamental in Python and Evennia. -The `me` object also contains a lot of useful resources for doing -things with that object. We access those resources with '`.`'. + > py print("This is a " + "breaking change.") + This is a breaking change. -One such resource is `msg`, which works like `print` except it sends the text to the object it -is attached to. So if we, for example, had an object `you`, doing `you.msg(...)` would send a message -to the object `you`. +A string multiplied with a number will repeat that string as many times: + + > py print("|" + "-" * 40 + "|") + |----------------------------------------| + + or + + > py print("A" + "a" * 5 + "rgh!") + Aaaaaargh! + +While combining different strings is useful, even more powerful is the ability to modify the contents +of the string in-place. There are several ways to do this in Python and we'll show two of them here. The first +is to use the `.format` _method_ of the string: ```sidebar:: Functions and Methods Function: - Stand-alone in a python module, like `print()` + Something that performs and action when you `call` it with zero or more `arguments`. A function + is stand-alone in a python module, like `print()` Method: - Something that sits "on" an object, like `.msg()` + A function that sits "on" an object, like `.format()`. ``` -For now, `print` and `me.msg` behaves the same, just remember that `print` is mainly used for -debugging and `.msg()` will be more useful for you in the future. +A method can be thought of as a resource "on" another object. The method knows on which object it +sits and can thus affect it in various ways. You access it with the period `.`. In this case, the +string has a resource `format(...)` that modifies it. More specifically, it replaced the `{}` marker +inside the string with the value passed to the format. You can do so many times: -For fun, try printing other things. Also try this: - > py self.msg("|rThis is red text!") + + > py print("This is a {} idea!".format("bad")) + This is a bad idea! + +or + + > py print("This is the {} and {} {} idea!".format("first", "second", "great")) + This is the first and second great idea! -Adding that `|r` at the start will turn our output red. Enter the command `color ansi` or `color xterm` -to see which colors are available. +> Note the double-parenthesis at the end - the first closes the `format(...` method and the outermost +closes the `print(...`. Not closing them will give you a scary `SyntaxError`. We will talk a +little more about errors in the next section, for now just fix until it prints as expected. + +Here we passed three comma-separated strings as _arguments_ to the string's `format` method. These +replaced the `{}` markers in the same order as they were given. + +The input does not have to be strings either: + + > py print("STR: {}, DEX: {}, INT: {}".format(12, 14, 8)) + STR: 12, DEX: 14, INT: 8 + +To separate two Python instructions on the same line, you use the semi-colon, `;`. Try this: + + > py a = "awesome sauce" ; print("This is {}!".format(a)) + This is awesome sauce! + +```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 + run in 'verbatim' mode or to remap to use some other separator than `;`. If you still have + trouble, just use the Evennia web client for now. In real Python code you'll pretty much never use + the semi-colon. +``` + +What happened here was that we _assigned_ the string `"awesome sauce"` to a _variable_ we chose +to name `a`. In the next statement, Python remembered what `a` was and we passed that into `format()` +to get the output. If you replaced the value of `a` with something else in between, _that_ would be printed +instead. + +Here's the stat-example again, moving the stats to variables (here we just set them, but in a real +game they may be changed over time, or modified by circumstance): + + > py str, dex, int = 13, 14, 8 ; print("STR: {}, DEX: {}, INT: {}".format(stren, dex, int)) + STR: 13, DEX: 14, INT: 8 + +The point is that even if the values of the stats change, the print() statement would not change - it just keeps +pretty-printing whatever is given to it. + +Using `.format()` is convenient (and there is a [lot more](https://www.w3schools.com/python/ref_string_format.asp) +you can do with it). But the _f-string_ can be even more convenient. An +f-string looks like a normal string ... except there is an `f` front of it, like this: + + f"this is now an f-string." + +An f-string on its own is just like any other string. But let's redo the example we did before, using an f-string: + + > py a = "awesome sauce" ; print(f"This is {a}!") + This is awesome sauce! + +We could just insert that `a` variable directly into the f-string using `{a}`. Fewer parentheses to +remember and arguable easier to read as well. + + > py str, dex, int = 13, 14, 8 ; print(f"STR: {str}, DEX: {dex}, INT: {int}") + STR: 13, DEX: 14, INT: 8 + +We will be exploring more complex string concepts when we get to creating Commands and need to +parse and understand player input. + +Python itself knows nothing about colored text, this is an Evennia thing. Evennia supports the +standard color schemes of traditional MUDs. + + > py print("|rThis is red text!|n This is normal color.") + +Adding that `|r` at the start will turn our output bright red. `|R` will make it dark red. `|n` +gives the normal text color. You can also use RGB (Red-Green-Blue) values from 0-5 (Xterm256 colors): + + > py print("|043This is a blue-green color.|[530|003 This is dark blue text on orange background.") + +> If you don't see the expected color, your client or terminal may not support Xterm256 (or + color at all). Use the Evennia webclient. + +Use the commands `color ansi` or `color xterm` to see which colors are available. Experiment! ### Importing code from other modules -As we saw in the previous section, we could use `me.msg` to access the `msg` method on `me`. This -use of the full-stop character is used to access all sorts of resources, including that in other -Python modules. If you've been following along, this is what we've referred to as the "Python path". +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, +including that in other Python modules. Keep your game running, then open a text editor of your choice. If your game folder is called `mygame`, create a new text file `test.py` in the subfolder `mygame/world`. This is how the file @@ -130,16 +219,12 @@ path - Evennia handles this for us. When you import the module, the top "level" of it will execute. In this case, it will immediately print "Hello World". -> If you look in the folder you'll also often find new files ending with `.pyc`. These are compiled -Python binaries that Python auto-creates when running code. Just ignore them, you should never edit -those anyway. - Now try to run this a second time: > py import world.test -You will *not* see any output this second time or any subsequent times! This is not a bug. Rather -it is because Python is being clever - it stores all imported modules and to be efficient it will +You will *not* see any output this second time or any subsequent times! This is not a bug. Rather +it is because of how Python importing works - it stores all imported modules and will avoid importing them more than once. So your `print` will only run the first time, when the module is first imported. @@ -159,29 +244,124 @@ not very useful. > We'll get back to more advanced ways to import code in later tutorial sections - this is an > important topic. But for now, let's press on and resolve this particular problem. -### Parsing Python errors -Running print only on import is not too helpful. We want to print whenever we like to! -Go back to your `test.py` file and erase the single `print` statement you had. Replace -it with this instead: +### Our first own function + +We want to be able to print our hello-world message at any time, not just once after a server +reload. Change your `mygame/world/test.py` file to look like this: ```python -me.msg("Hello World!") +def hello_world(): + print("Hello World!") ``` -As you recall we used this with `py` earlier - it echoed "Hello World!" in-game. -Save your file and `reload` your server - this makes sure Evennia knows to re-import -it (with our new, fresh code this time). +As we are moving to multi-line Python code, there are some important things to remember: -To test it, import it using `py` as before +- Capitalization matters in Python. It must be `def` and not `DEF`, `who` is not the same as `Who`. +- Indentation matters in Python. The second line must be indented or it's not valid code. You should +also use a consistent indentation length. We *strongly* recommend that you, for your own sanity's sake, +set up your editor to always indent *4 spaces* (**not** a single tab-character) when you press the TAB key. - > py import world.test +So about that function. Line 1: + +- `def` is short for "define" and defines a *function* (or a *method*, if sitting on an object). +This is a [reserved Python keyword](https://docs.python.org/2.5/ref/keywords.html); try not to use +these words anywhere else. +- A function name can not have spaces but otherwise we could have called it almost anything. We call +it `hello_world`. Evennia follows [Python's standard naming style](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#a-quick-list-of-code-style-points) +with lowercase letters and underscores. We recommend you do the same. +- The colon (`:`) at the end of line 1 indicates that the header of the function is complete. + +Line 2: + +- The indentation marks the beginning of the actual operating code of the function (the function's +*body*). If we wanted more lines to belong to this function those lines would all have to +start at least at this indentation level. + +Now let's try this out. First `reload` your game to have it pick up +our updated Python module, then import it. + + > reload + > py import world.test + +Nothing happened! That is because the function in our module won't do anything just by importing it (this +is what we wanted). It will only act when we *call* it. So we need to first import the module and then access the +function within: + + > py import world.test ; world.test.hello_world() + Hello world! + +There is our "Hello World"! As mentioned earlier, use use semi-colon to put multiple +Python-statements on one line. Note also the previous warning about mud-clients using the `;` to their +own ends. + +So what happened there? First we imported `world.test` as usual. But this time we continued and +accessed the `hello_world` function _inside_ the newly imported module. + +By adding `()` to the `hello_world` function we _call_ it, that is we run the body of the function and +print our text. We can now redo this as many times as we want without having to `reload` in between: + + + > py import world.test ; world.test.hello_world() + Hello world! + > py import world.test ; world.test.hello_world() + Hello world! + +> **Extra Credit:** As an exercise, try to pass something else into `hello_world`. Try for example +>to pass the number `5` or the string `"foo"`. You'll get errors telling you that they don't have +>the attribute `msg`. They don't care about `me` itself not being a string or a number. If you are +>familiar with other programming languages (especially C/Java) you may be tempted to start *validating* +>`who` to make sure it's of the right type before you send it. This is usually not recommended in Python. +>Python philosophy is to [handle](https://docs.python.org/2/tutorial/errors.html) the error if it happens +>rather than to add a lot of code to prevent it from happening. See [duck typing](https://en.wikipedia.org/wiki/Duck_typing) +>and the concept of _Leap before you Look_. + +### 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 +to an actual player, `print` won't do, because it doesn't know _who_ to send to. Try this: + + > py me.msg("Hello world!") + Hello world! + +This looks the same as the `print` result, but we are now actually messaging a specific *object*, +`me`. The `me` is a shortcut to 'us', the one running the `py` command. It is not some special +Python thing, but something Evennia just makes available in the `py` command for convenience +(`self` is an alias). + +The `me` is an example of an *Object instance*. Objects are fundamental in Python and Evennia. +The `me` object also contains a lot of useful resources for doing +things with that object. We access those resources with '`.`'. + +One such resource is `msg`, which works like `print` except it sends the text to the object it +is attached to. So if we, for example, had an object `you`, doing `you.msg(...)` would send a message +to the object `you`. + +For now, `print` and `me.msg` behaves the same, just remember that `print` is mainly used for +debugging and `.msg()` will be more useful for you in the future. + + +### 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: + +```python +def hello_world(): + me.msg("Hello World!") +``` + +Save your file and `reload` your server to tell Evennia to re-import new code, +then run it like before: + + > py import world.test ; world.test.hello_world() No go - this time you get an error! ```python -File "./world/test.py", line 1, in - me.msg("Hello world!") +File "./world/test.py", line 2, in hello_world + me.msg("Hello World!") NameError: name 'me' is not defined ``` @@ -216,92 +396,258 @@ 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`. -### Our first own function +### Passing arguments to functions -Let's see if we can resolve that `NameError` from the previous section. We know that `me` is defined -at the time we use the `py` command. We know this because if we do `py me.msg("Hello World!")` -directly in-game it works fine. What if we could *send* that `me` to the `test.py` module so it -knows what it is? One way to do this is with a *function*. - -Change your `mygame/world/test.py` file to look like this: +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. +Go back to your `test.py` and change it to this: ```python def hello_world(who): who.msg("Hello World!") ``` +We now added an _argument_ to the function. We could have named it anything. Whatever `who` is, +we will call a method `.msg()` on it. -As we are moving to multi-line Python code, there are some important things to remember: - -- Capitalization matters in Python. It must be `def` and not `DEF`, `who` is not the same as `Who`. -- Indentation matters in Python. The second line must be indented or it's not valid code. You should -also use a consistent indentation length. We *strongly* recommend that you, for your own sanity's sake, -set up your editor to always indent *4 spaces* (**not** a single tab-character) when you press the TAB key. - -So about that function. Line 1: - -- `def` is short for "define" and defines a *function* (or a *method*, if sitting on an object). -This is a [reserved Python keyword](https://docs.python.org/2.5/ref/keywords.html); try not to use -these words anywhere else. -- A function name can not have spaces but otherwise we could have called it almost anything. We call -it `hello_world`. Evennia follows [Python's standard naming style](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#a-quick-list-of-code-style-points) -with lowercase letters and underscores. We recommend you do the same. -- `who` is what we call the *argument* to our function. Arguments are variables _passed_ to the -function. We could have named it anything and we could also have multiple arguments separated by -commas. What `who` is depends on what we pass to this function when we *call* it later (hint: we'll -pass `me` to it). -- The colon (`:`) at the end of line 1 indicates that the header of the function is complete. - -Line 2: - -- The indentation marks the beginning of the actual operating code of the function (the function's -*body*). If we wanted more lines to belong to this function those lines would all have to -start at least at this indentation level. -- In the function body we take the `who` argument and treat it as we would have treated `me` - earlier- we expect it to have a `.msg` method we can use to send "Hello World" to. - -Now let's try this out. First `reload` your game to have it pick up -our updated Python module when we reload. - - > reload - > py import world.test - -Nothing happened! That is because the function in our module won't do anything just by importing it. -It will only act when we *call* it. So we need to first import the module and then access the -function within: +As usual, `reload` the server to make sure the new code is available. > py import world.test ; world.test.hello_world(me) - Hello world! + Hello World! + +Now it worked. We _passed_ `me` to our function. It will appear inside the function renamed as `who` and +now the function works and prints as expected. Note how the `hello_world` function doesn't care _what_ you +pass into it as long as it has a `.msg()` method on it. So you could reuse this function over and over for other +suitable targets. -There is our "Hello World"! Using `;` is the way to put multiple Python-statements on one line. +### Finding others to send to -```warning:: MUD clients and semi-colon +Let's wrap up this first Python `py` crash-course by finding someone else to send to. - A common issue is that some MUD clients use the semi-colon `;` to split client-inputs - into separate sends. If so, you'll get a `NameError` above, stating that - `world` is not defined. Check so you understand why this is! Most clients allow you to - remap to use some other separator than `;`. You can use the Evennia web client if this - problem remains. +In Evennia's `contrib/` folder (`evennia/contrib/tutorial_examples/mirror.py`) is a handy little +object called the `TutorialMirror`. The mirror will echo whatever is being sent to it to +the room it is in. + +On the game command-line, let's create a mirror: + + > create/drop mirror:contrib.tutorial_examples.mirror.TutorialMirror + +```sidebar:: Creating objects + + The `create` command was first used to create boxes in the + `Building Stuff `_ tutorial. Note how it + uses a "python-path" to describe where to load the mirror's code from. ``` -So what happened there? First we imported `world.test` as usual. But this time we continued and -accessed the `hello_world` function _inside_ the newly imported module. +A mirror should appear in your location. -We *call* the function with `me`, which becomes the `who` variable we use inside -the `hello_function` (they are be the same object). And since `me.msg` works, so does `who.msg` -inside the function. + > look mirror + The mirror shows your reflection: + This is User #1 -> **Extra Credit:** As an exercise, try to pass something else into `hello_world`. Try for example ->to pass the number `5` or the string `"foo"`. You'll get errors telling you that they don't have ->the attribute `msg`. They don't care about `me` itself not being a string or a number. If you are ->familiar with other programming languages (especially C/Java) you may be tempted to start *validating* ->`who` to make sure it's of the right type before you send it. This is usually not recommended in Python. ->Python philosophy is to [handle](https://docs.python.org/2/tutorial/errors.html) the error if it happens ->rather than to add a lot of code to prevent it from happening. See [duck typing](https://en.wikipedia.org/wiki/Duck_typing) ->and the concept of _Leap before you Look_. +What you are seeing is actually your own avatar in the game, the same thing that is available as `me` in the `py` +command. + +What we are aiming for now is the equivalent of `mirror.msg("Mirror Mirror on the wall")`. But the first thing that +comes to mind will not work: + + > py mirror.msg("Mirror, Mirror on the wall ...") + NameError: name 'mirror' is not defined. + +This is not surprising: Python knows nothing about "mirrors" or locations or anything. The `me` we've been using +is, as mentioned, just a convenient thing the Evennia devs makes available to the `py` command. They couldn't possibly +predict that you wanted to talk to mirrors. + +Instead we will need to _search_ for that `mirror` object before we can send to it. +Make sure you are in the same location as the mirror and try: + + > py me.search("mirror") + mirror + +`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 + + 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 + as a machine - you put something in and out comes a result you can use. In the case + of `me.search`, it will perform a database search and spit out the object it finds. +``` + + > py me.search("dummy") + Could not find 'dummy'. + +Wanting to find things in the same location is very common, but as we continue we'll +find that Evennia provides ample tools for tagging, searching and finding things from all over your game. + +Now that we know how to find the 'mirror' object, we just need to use that instead of `me`! + + > py mirror = self.search("mirror") ; mirror.msg("Mirror, Mirror on the wall ...") + The mirror echoes back to you: + "Mirror, Mirror on the wall ..." + +The mirror is useful for testing because its `.msg` method just echoes whatever is sent to it back to the room. More common +would be to talk to a player character, in which case the text you sent would have appeared in their game client. -This gives you some initial feeling for how to run Python and import Python modules. We also -tried to put a module in `module/world/`. Now let's look at the rest of the stuff you've got -inside that `mygame/` folder ... +### 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 +inside Evennia. + + > 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] + +(the details of the output will vary with your Python version and OS). You are now in python interpreter mode. It means +that _everything_ you insert from now on will become a line of Python (you can no longer look around or do other +commands). + + > print("Hello World") + + >>> print("Hello World") + Hello World + [py mode - quit() to exit] + +Note that we didn't need to put `py` in front now. The system will also echo your input (that's the bit after +the `>>>`). For brevity in this tutorual we'll turn the echo off. First exit `py` and then start again with the +`/noecho` flag. + + > quit() + Closing the Python console. + > py/noecho + 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] + +```sidebar:: interactive py + + - Start with `py`. + - Use `py/noecho` if you don't want your input to be echoed for every line. + - All your inputs will now be interpreted as Python code. + - Exit with `quit()`. +``` + +We can now enter multi-line Python code: + + > a = "Test" + > print(f"This is a {a}."} + This is a Test. + +Let's try to define a function: + + > def hello_world(who, txt): + ... + > who.msg(txt) + ... + > + [py mode - quit() to exit] + +Some important things above: + +- Definining a function with `def` means we are starting a new code block. Python works so that you mark the content + of the block with indention. So the next line must be manually indented (4 spaces is a good standard) in order + for Python to know it's part of the function body. +- We expand the `hello_world` function with another argument `txt`. This allows us to send any text, not just + "Hello World" over and over. +- To tell `py` that no more lines will be added to the function body, we end with an empty input. When + the normal prompt on how to exit returns, we know we are done. + +Now we have defined a new function. Let's try it out: + + > hello_world(me, "Hello world to me!") + Hello world to me! + +The `me` is still available to us, so we pass that as the `who` argument, along with a little longer +string. Let's combine this with searching for the mirror. + + > mirror = me.search("mirror") + > hello_world(mirror, "Mirror, Mirror on the wall ...") + The mirror echoes back to you: + "Mirror, Mirror on the wall ..." + +Exit the `py` mode with + + > quit() + Closing the Python console. + +## Other ways to test Python code + +The `py` command is very powerful for experimenting with Python in-game. It's great for quick testing. +But you are still limited to working over telnet or the webclient, interfaces that doesn't know anything +about Python per-se. + +Outside the game, go to the terminal where you ran Evennia (or any terminal where the `evennia` command +is available). + +- `cd` to your game dir. +- `evennia shell` + +A Python shell opens. This works like `py` did inside the game, with the exception that you don't have +`me` available out of the box. If you want `me`, you need to first find yourself: + + > import evennia + > me = evennia.search_object("YourChar")[0] + +Here we make use of one of evennia's search functions, available by importing `evennia` directly. +We will cover more advanced searching later, but suffice to say, you put your own character name instead of +"YourChar" above. + +> The `[0]` at the end is because `.search_object` returns a list of objects and we want to +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 + +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. + + pip install ipython + +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: + + > import evennia + > evennia. + +That is, enter `evennia.` and then press the TAB key - you will be given a list of all the resources +available on the `evennia` object. This is great for exploring what Evennia has to offer. For example, +use your arrow keys to scroll to `search_object()` to fill it in. + + > evennia.search_object? + +Adding a `?` and pressing return will give you the full documentation for `.search_object`. Use `??` if you +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 + + 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 + Python code, we need to save it in a Python module, like we did for `world/test.py`. +``` + + +## 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 +python inside and outside of the game is an important skill for testing and debugging, but in +practice you will be writing most your code in Python modules. + +To that end we also created a first new Python module in the `mygame/` game dir, then imported and used it. +Now let's look at the rest of the stuff you've got going on inside that `mygame/` folder ... [prev lesson](Tutorial-World-Introduction) | [next lesson](Gamedir-Overview) diff --git a/docs/source/Setup/Client-Support-Grid.md b/docs/source/Setup/Client-Support-Grid.md index c65be13402..7ce187285c 100644 --- a/docs/source/Setup/Client-Support-Grid.md +++ b/docs/source/Setup/Client-Support-Grid.md @@ -44,28 +44,28 @@ as รน character. Also seems to run the `version` command on connection, which wi `MULTISESSION_MODES` above 1. [KildClient][22] | 2.11.1 | No known issues. -[1]: https://github.com/evennia/evennia/wiki/Web%20features#web-client -[2]: https://github.com/evennia/evennia/issues?utf8=%E2%9C%93&q=client+status%3Dopen+] -[3]: http://tintin.sourceforge.net/ -[4]: http://tinyfugue.sourceforge.net/ -[5]: http://mushclient.com/ -[6]: http://forums.zuggsoft.com/index.php?page=4&action=file&file_id=65 -[7]: http://forums.zuggsoft.com/index.php?page=4&action=category&cat_id=11 -[8]: http://www.potatomushclient.com/ -[9]: http://www.mudlet.org/ -[10]: https://archive.org/details/tucows_196173_SimpleMU_MU_Client -[11]: http://www.riverdark.net/atlantis/ -[12]: https://sourceforge.net/projects/g-mud/ -[13]: http://www.beipmu.com/ -[14]: https://itunes.apple.com/us/app/mudrammer-a-modern-mud-client/id597157072 -[15]: https://itunes.apple.com/us/app/mudmaster/id341160033 -[16]: http://bt.happygoatstudios.com/ -[17]: https://play.google.com/store/apps/details?id=com.crap.mukluk -[18]: https://github.com/GNOME/gnome-mud -[19]: https://spyrit.ierne.eu.org/ -[20]: http://jamochamud.org/ -[21]: http://duckclient.com/ -[22]: https://www.kildclient.org/ +[1](https://github.com/evennia/evennia/wiki/Web%20features#web-client) +[2](https://github.com/evennia/evennia/issues?utf8=%E2%9C%93&q=client+status%3Dopen+]) +[3](http://tintin.sourceforge.net/) +[4](http://tinyfugue.sourceforge.net/) +[5](http://mushclient.com/) +[6](http://forums.zuggsoft.com/index.php?page=4&action=file&file_id=65) +[7](http://forums.zuggsoft.com/index.php?page=4&action=category&cat_id=11) +[8](http://www.potatomushclient.com/) +[9](http://www.mudlet.org/) +[10](https://archive.org/details/tucows_196173_SimpleMU_MU_Client) +[11](http://www.riverdark.net/atlantis/) +[12](https://sourceforge.net/projects/g-mud/) +[13](http://www.beipmu.com/) +[14](https://itunes.apple.com/us/app/mudrammer-a-modern-mud-client/id597157072) +[15](https://itunes.apple.com/us/app/mudmaster/id341160033) +[16](http://bt.happygoatstudios.com/) +[17](https://play.google.com/store/apps/details?id=com.crap.mukluk) +[18](https://github.com/GNOME/gnome-mud) +[19](https://spyrit.ierne.eu.org/) +[20](http://jamochamud.org/) +[21](http://duckclient.com/) +[22](https://www.kildclient.org/) ## Workarounds for client issues: diff --git a/docs/source/Setup/Online-Setup.md b/docs/source/Setup/Online-Setup.md index 0438ea1fb1..9552cb8767 100644 --- a/docs/source/Setup/Online-Setup.md +++ b/docs/source/Setup/Online-Setup.md @@ -406,16 +406,16 @@ servers with this option as they don't have a lot of support. *Please help us expand this list.* [1]: http:silvren.com -[2]: https://www.digitalocean.com/pricing -[3]: https://aws.amazon.com/pricing/ -[4]: http://www.genesismuds.com/ -[5]: https://www.host1plus.com/ -[6]: https://www.scaleway.com/ -[7]: https://lowendbox.com/ -[8]: https://www.lowendtalk.com -[9]: https://amazonlightsail.com -[10]: https://prgmr.com/ -[11]: https://www.linode.com/ +[2](https://www.digitalocean.com/pricing) +[3](https://aws.amazon.com/pricing/) +[4](http://www.genesismuds.com/) +[5](https://www.host1plus.com/) +[6](https://www.scaleway.com/) +[7](https://lowendbox.com/) +[8](https://www.lowendtalk.com) +[9](https://amazonlightsail.com) +[10](https://prgmr.com/) +[11](https://www.linode.com/) ## Cloud9 diff --git a/docs/source/conf.py b/docs/source/conf.py index ae9f20539a..907329cb2c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -116,19 +116,23 @@ _github_issue_choose = "https://github.com/evennia/evennia/issues/new/choose" def url_resolver(url): + """ + Convert urls by catching special markers. + """ githubstart = "github:" apistart = "api:" - choose_issue = ("feature-request", "report-bug", "issue", "bug-report") + choose_issue = "github:issue" - if url.lower().strip() in choose_issue: + if url.endswith(choose_issue): return _github_issue_choose - elif url.startswith(githubstart): - urlpath = url[len(githubstart):] + elif githubstart in url: + urlpath = url[url.index(githubstart) + len(githubstart):] if not (urlpath.startswith("develop/") or urlpath.startswith("master")): urlpath = "master/" + urlpath return _github_code_root + urlpath - elif url.startswith(apistart): - return "api/" + url[len(apistart):] + ".html" + elif apistart in url: + urlpath = url[url.index(apistart) + len(apistart):] + return "api/" + urlpath + ".html" return url diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 2c67951f73..5ef7eef992 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -392,7 +392,9 @@ class CmdPy(COMMAND_DEFAULT_CLASS): if noecho: prompt = "..." if console.push(line) else main_prompt else: - prompt = line if console.push(line) else f"{line}\n{main_prompt}" + if line: + self.caller.msg(f">>> {line}") + prompt = line if console.push(line) else main_prompt except SystemExit: break self.msg("|gClosing the Python console.|n")