<<

Evennia Documentation Release 0.9

The Evennia community

Jun 14, 2020

Contents

1 Evennia Introduction 3 1.1 Can I test it somewhere?...... 4 1.2 Brief summary of features...... 4 1.3 What you need to know to work with Evennia...... 5

2 How To Get And Give Help 7 2.1 How to get Help...... 7 2.2 How to give Help...... 7

3 Contributing 9 3.1 Spreading the word...... 9 3.2 Donations...... 9 3.3 Help with Documentation...... 9 3.4 Contributing through a forked repository...... 10 3.5 Contributing with Patches...... 10 3.6 Contributing with Contribs...... 10

4 Soft Code 13 4.1 Examples of Softcode...... 13 4.2 Problems with Softcode...... 14 4.3 Changing Times...... 14 4.4 Our Solution...... 14 4.5 Your Solution...... 15

5 Using MUX as a Standard 17 5.1 Documentation policy...... 17

6 Game Planning 19 6.1 Planning (step 1)...... 19 6.2 Coding (step 2)...... 21 6.3 World Building (step 3)...... 22 6.4 Alpha Release...... 22 6.5 Beta Release/Perpetual Beta...... 23 6.6 Congratulate yourself!...... 23

7 Installation and setup 25 7.1 Getting Started...... 25

i 7.2 Start Stop Reload...... 36 7.3 Choosing An SQL Server...... 40 7.4 Apache Config...... 44 7.5 Updating Your Game...... 48 7.6 Server Conf...... 50 7.7 Online Setup...... 52

8 Admin Documentation 61 8.1 Banning...... 61 8.2 IRC...... 64 8.3 RSS...... 65 8.4 Text Encodings...... 66 8.5 Internationalization...... 67 8.6 Client Support Grid...... 69

9 Builder Documentation 73 9.1 Building Quickstart...... 73 9.2 TextTags...... 78 9.3 Building Permissions...... 83 9.4 Connection Screen...... 84 9.5 Batch Processors...... 85 9.6 Batch Command Processor...... 86 9.7 Batch Code Processor...... 90 9.8 Spawner...... 93 9.9 Tutorial World Introduction...... 98

10 Developer Basics 101 10.1 Coding Introduction...... 101 10.2 Execute Python Code...... 103 10.3 Quirks...... 105 10.4 Licensing...... 107

11 Server Components 109 11.1 Directory Overview...... 109 11.2 Portal And Server...... 110 11.3 Sessions...... 111 11.4 Messagepath...... 114 11.5 OOB...... 118 11.6 Inputfuncs...... 121 11.7 Custom Protocols...... 125 11.8 Commands...... 129 11.9 Command Sets...... 141 11.10 Typeclasses...... 147 11.11 Objects...... 152 11.12 Scripts...... 156 11.13 Accounts...... 163 11.14 Communications...... 165 11.15 Signals...... 167 11.16 Attributes...... 169 11.17 Nicks...... 177 11.18 Advanced note...... 178 11.19 Tags...... 179 11.20 Locks...... 181 11.21 Permissions...... 186 11.22 Help System...... 190 11.23 TickerHandler...... 192 11.24 MonitorHandler...... 194 11.25 EvMenu...... 195 11.26 EvMore...... 213 11.27 EvEditor...... 214 11.28 Web Features...... 217

12 Coding utilities 221 12.1 New Models...... 221 12.2 Coding Utils...... 225 12.3 Version Control...... 230 12.4 Profiling...... 242 12.5 Unit Testing...... 245 12.6 Setting up PyCharm...... 251 12.7 Using Travis...... 257 12.8 Async Process...... 258

13 Tutorials 263 13.1 Python basic introduction...... 263 13.2 First Steps Coding...... 267 13.3 Tutorial for basic MUSH like game...... 272 13.4 Adding Command Tutorial...... 283 13.5 Adding Object Typeclass Tutorial...... 286 13.6 Command Prompt...... 288 13.7 NPC shop Tutorial...... 291 13.8 Static In Game Map...... 296 13.9 Dynamic In Game Map...... 303 13.10 Implementing a game rule system...... 311 13.11 Turn based Combat System...... 316 13.12 Evennia for roleplaying sessions...... 325 13.13 Zones...... 337 13.14 Command Duration...... 338 13.15 Command Cooldown...... 345 13.16 Mass and weight for objects...... 347 13.17 Default Exit Errors...... 349 13.18 Gametime Tutorial...... 351 13.19 Coordinates...... 356 13.20 Dialogues in events...... 362 13.21 A voice operated elevator using events...... 366 13.22 Add a simple new web page...... 374 13.23 Web Tutorial...... 376 13.24 Web Character View Tutorial...... 378 13.25 Help System Tutorial...... 381 13.26 Add a wiki on your ...... 389 13.27 Web Character Generation...... 394 13.28 Bootstrap & Evennia...... 404 13.29 Bootstrap Components and Utilities...... 406 13.30 Evennia for Diku Users...... 408 13.31 Evennia for MUSH Users...... 411

14 Appendix 415 14.1 Links...... 415 14.2 Default Command Help...... 418 14.3 Evennia Devel...... 467

iii iv Evennia Documentation, Release 0.9

This is the manual of Evennia, the open source Python MU* creation system. You should hopefully find all you need to know about coding with, extending and using the code base among these pages. If you have further questions you are welcome to ask them in the Developer online chat or, if you don’ have time to hang around for an answer, on the Mailing list. Please note that this ReadTheDocs version of the documentation is auto-converted from the original documentation found in the Evennia wiki. It represents the wiki as it looked on Saturday March 28, 2020, at 09:29 (GMT+1). The converted version is supplied as a service for those wanting to have access to the documentation in an offline format, on their tablet or printed on paper. The conversion unfortunately means that there are occational conversion artifacts. Notably inter-documentation links and images may not work. This should hopefully not affect readability or understanding, but is something to keep in mind.

Contents 1 Evennia Documentation, Release 0.9

2 Contents CHAPTER 1

Evennia Introduction

A MUD (originally Multi-User Dungeon, with later variants Multi-User Dimension and Multi-User Do- main) is a multiplayer real-time virtual world described primarily in text. MUDs combine elements of role-playing games, hack and , , interactive fiction and online chat. Players can read or view descriptions of rooms, objects, other players, non-player characters, and actions performed in the virtual world. Players typically interact with each other and the world by typing commands that resemble a natural language. - Wikipedia If you are reading this, it’ quite likely you are dreaming of creating and running a text-based massively-multiplayer game (MUD/MUX/MUSH etc) of your very own. You might just be starting to think about it, or you might have lugged around that perfect game in your mind for years . . . you know just how good it would be, if you could only make it come to reality. We know how you feel. That is, after all, why Evennia came to be. Evennia is in principle a MUD-building system: a bare-bones Python codebase and server intended to be highly extendable for any style of game. “Bare-bones” in this context means that we try to impose as few game-specific things on you as possible. So whereas we for convenience offer basic building blocks like objects, characters, rooms, default commands for building and administration etc, we don’t prescribe any combat rules, AI, races, skills, character classes or other things that will be different from game to game anyway. It is possible that we will offer some such systems as contributions in the future, but these will in that case all be optional. What we do however, is to provide a solid foundation for all the boring , networking, and behind-the-scenes administration stuff that all online games need whether they like it or not. Evennia is fully persistent, that means things you drop on the ground somewhere will still be there a dozen server reboots later. Through Django we support a large variety of different database systems (a database is created for you automatically if you use the defaults). Using the full power of Python throughout the server offers some distinct advantages. All your coding, from object definitions and custom commands to AI scripts and economic systems is done in normal Python modules rather than some ad-hoc . The fact that you script the game in the same high-level language that you code it in allows for very powerful and custom game implementations indeed. The server ships with a default set of player commands that are similar to the MUX command set. We do not aim specifically to be a MUX server, but we had to pick some default to go with (see this for more about our original motivations). It’s easy to remove or add commands, or to have the command syntax mimic other systems, like Diku, LP, MOO and so on. Or why not create a new and better command system of your own design.

3 Evennia Documentation, Release 0.9

1.1 Can I test it somewhere?

Evennia’s demo server can be found at demo.evennia.com. If you prefer to connect to the demo via your own telnet client you can do so at silvren.com, port 4280. Here is a screenshot. Once you installed Evennia yourself it comes with its own tutorial - this shows off some of the possibilities and gives you a small single-player to play. The tutorial takes only one single in-game command to install as explained here.

1.2 Brief summary of features

1.2.1 Technical

• Game development is done by the server importing your normal Python modules. Specific server features are implemented by overloading hooks that the engine calls appropriately. • All game entities are simply Python classes that handle database negotiations behind the scenes without you needing to worry. • Command sets are stored on individual objects (including characters) to offer unique functionality and object- specific commands. Sets can be updated and modified on the fly to expand/limit player input options during play. • Scripts are used to offer asynchronous/timed execution abilities. Scripts can also be persistent. There are easy mechanisms to thread particularly long-running processes and built-in ways to start “tickers” for games that wants them. • In-game communication channels are modular and can be modified to any functionality, including mailing systems and full logging of all messages. • Server can be fully rebooted/reloaded without users disconnecting. • An Account can freely connect/disconnect from game-objects, offering an easy way to implement multi- character systems and puppeting. • Each Account can optionally control multiple Characters/Objects at the same time using the same login infor- mation. • of individual objects via a prototypes-like system. • Tagging can be used to implement zones and object groupings. • All source code is extensively documented. • Unit-testing suite, including tests of default commands and plugins.

1.2.2 Default content

• Basic classes for Objects, Characters, Rooms and Exits • Basic login system, using the Account’s login name as their in-game Character’s name for simplicity • “MUX-like” command set with administration, building, puppeting, channels and social commands • In-game Tutorial • Contributions folder with working, but optional, code such as alternative login, menus, character generation and more

4 Chapter 1. Evennia Introduction Evennia Documentation, Release 0.9

1.2.3 Standards/Protocols supported

• TCP/websocket HTML5 browser web client, with ajax/comet fallback for older browsers • Telnet and Telnet + SSL with -specific extensions (MCCP, MSSP, TTYPE, MSDP, GMCP, MXP links) • ANSI and xterm256 colours • SSH • HTTP - Website served by in-built webserver and connected to same database as game. • IRC - external IRC channels can be connected to in-game chat channels • RSS feeds can be echoed to in-game channels (things like Twitter can easily be added) • Several different supported (SQLite3, MySQL, PostgreSQL, . . . ) For more extensive feature information, see the Developer Central.

1.3 What you need to know to work with Evennia

Assuming you have Evennia working (see the quick start instructions) and have gotten as far as to start the server and connect to it with the client of your choice, here’s what you need to know depending on your skills and needs.

1.3.1 I don’t know (or don’t want to do) any programming - I just want to run a game!

Evennia comes with a default set of commands for the Python newbies and for those who need to get a game running now. Stock Evennia is enough for running a simple ‘’-type game - you can build and describe rooms and basic objects, have chat channels, do emotes and other things suitable for a social or free-form MU*. Combat, mobs and other game elements are not included, so you’ll have a very basic game indeed if you are not willing to do at least some coding.

1.3.2 I know basic Python, or I am willing to learn

Evennia’s source code is extensively documented and is viewable online. We also have a comprehensive online manual with lots of examples. But while Python is considered a very easy to get into, you do have a learning curve to climb if you are new to programming. You should probably sit down with a Python beginner’s tutorial (there are plenty of them on the web if you look around) so you at least know what you are seeing. See also our link page for some reading suggestions. To efficiently code your dream game in Evennia you don’t need to be a Python guru, but you do need to be able to read example code containing at least these basic Python features:

• Importing and using python modules • Using variables, conditional statements, loops and functions • Using lists, dictionaries and list comprehensions • Doing string handling and formatting • Have a basic understanding of object-oriented programming, using Classes, their methods and properties

1.3. What you need to know to work with Evennia 5 Evennia Documentation, Release 0.9

Obviously, the more things you feel comfortable with, the easier time you’ll have to find your way. With just basic knowledge you should be able to define your own Commands, create custom Objects as well as make your world come alive with basic Scripts. You can definitely build a whole advanced and customized game from extending Evennia’s examples only.

1.3.3 I know my Python stuff and I am willing to use it!

Even if you started out as a Python beginner, you will likely get to this point after working on your game for a while. With more general knowledge in Python the full power of Evennia opens up for you. Apart from modifying commands, objects and scripts, you can develop everything from advanced mob AI and economic systems, through sophisticated combat and social mini games, to redefining how commands, players, rooms or channels themselves work. Since you code your game by importing normal Python modules, there are few limits to what you can accomplish. If you also happen to know some web programming (HTML, CSS, Javascript) there is also a web presence (a website and a mud web client) to play around with . . .

1.3.4 Where to from here?

From here you can continue browsing the online documentation to find more info about Evennia. Or you can jump into the Tutorials and get your hands dirty with code right away. You can also read the developer’s dev blog for many tidbits and snippets about Evennia’s development and structure. Some more hints: 1. Get engaged in the community. Make an introductory post to our mailing list/forum and get to know people. It’s also highly recommended you hop onto our Developer chat on IRC. This allows you to chat directly with other developers new and old as well as with the devs of Evennia itself. This chat is logged (you can find links on http://www.evennia.com) and can also be searched from the same place for discussion topics you are interested in. 2. Read the Game Planning wiki page. It gives some ideas for your work flow and the state of mind you should aim for - including cutting down the scope of your game for its first release. 3. Do the Tutorial for basic MUSH-like game carefully from beginning to end and try to understand what does what. Even if you are not interested in a MUSH for your own game, you will end up with a small (very small) game that you can build or learn from.

6 Chapter 1. Evennia Introduction CHAPTER 2

How To Get And Give Help

2.1 How to get Help

If you cannot find what you are looking for in the online documentation, here’s what to do: • If you think the documentation is not clear enough and are short on time, fill in our quick little online form and let us know - no login required. Maybe the docs need to be improved or a new tutorial added! Note that while this form is useful as a suggestion box we cannot answer questions or reply to you. Use the discussion group or chat (linked below) if you want feedback. • If you have trouble with a missing feature or a problem you think is a bug, go to the issue tracker and search to see if has been reported/suggested already. If you can’t find an existing entry create a new one. • If you need help, want to start a discussion or get some input on something you are working on, make a post to the discussions group This is technically a ‘mailing list’, but you don’t need to use -mail; you can post and read all messages just as easily from your browser via the online interface. • If you want more direct discussions with developers and other users, consider dropping into our IRC chat channel #evennia on the network. Please note however that you have to be patient if you don’t get any response immediately; we are all in very different time zones and many have busy personal lives. So you might have to hang around for a while - you’ll get noticed eventually!

2.2 How to give Help

Evennia is a completely non-funded project. It relies on the time donated by its users and developers in order to progress. The first and easiest way you as a user can help us out is by taking part in community discussions and by giving feedback on what is good or bad. Report bugs you find and features you lack to our issue tracker. Just the simple act of letting developers know you are out there using their program is worth a lot. Generally mentioning and reviewing Evennia elsewhere is also a nice way to spread the word. If you’ like to help develop Evennia more hands-on, here are some ways to get going:

7 Evennia Documentation, Release 0.9

• Look through our online documentation wiki and see if you can help improve or expand the documentation (even small things like fixing typos!). You don’t need any particular permissions to edit the wiki. • Send a message to our discussion group and/or our IRC chat asking about what needs doing, along with what your interests and skills are. • Take a look at our issue tracker and see if there’s something you feel like taking on. here are bugs that need fixes. At any given time there may also be some bounties open - these are issues members of the community has put up money to see fixed (if you want to put up a bounty yourself you can do so via our page on bountysource). • Check out the Contributing page on how to practically contribute with code using github. . . . And finally, if you want to help motivate and support development you can also drop some coins in the developer’s cup. You can make a donation via PayPal or, even better, become an Evennia patron on Patreon! This is a great way to tip your hat and show that you appreciate the work done with the server! Finally, if you want to encourage the community to resolve a particular

8 Chapter 2. How To Get And Give Help CHAPTER 3

Contributing

Wanna help out? Great! Here’s how.

3.1 Spreading the word

Even if you are not keen on working on the server code yourself, just spreading the word is a big help - it will help attract more people which leads to more feedback, motivation and interest. Consider writing about Evennia on your blog or in your favorite (relevant) forum. Write a review somewhere (good or bad, we like feedback either way). Rate it on places like ohloh. Talk about it to your friends . . . that kind of thing.

3.2 Donations

The best way to support Evennia is to become an Evennia patron. Evennia is a free, open-source project and any monetary donations you want to offer are completely voluntary. See it as a way of announcing that you appreciate the work done - a tip of the hat! A patron donates a (usually small) sum every month to show continued support. If this is not your thing you can also show your appreciation via a one-time donation (this is a PayPal link but you don’t need PayPal yourself).

3.3 Help with Documentation

Evennia depends heavily on good documentation and we are always looking for extra eyes and hands to improve it. Even small things such as fixing typos are a great help!

9 Evennia Documentation, Release 0.9

The documentation is a wiki and as long as you have a GitHub account you can edit it. It can be a good idea to discuss in the chat or forums if you want to add new pages/tutorials. Otherwise, it goes a long way just pointing out wiki errors so we can fix them (in an Issue or just over chat/forum).

3.4 Contributing through a forked repository

We always need more eyes and hands on the code. Even if you don’t feel confident with tackling a bug or feature, just correcting typos, adjusting formatting or simply using the thing and reporting when stuff doesn’t make sense helps us a lot.

The most elegant way to contribute code to Evennia is to use GitHub to create a fork of the Evennia repository and make your changes to that. Refer to the Forking Evennia version control instructions for detailed instructions.

Once you have a fork set up, you can not only work on your own game in a separate branch, you can also commit your fixes to Evennia itself. Make separate branches for all Evennia additions you do - don’t edit your local master or develop branches directly. It will make your life a lot easier. If you have a change that you think is suitable for the main Evennia repository, you issue a ‘Pull Request‘_. This will let Evennia devs know you have stuff to share. Bug fixes should generally be done against the master branch of Evennia, while new features/contribs should go into the develop branch. If you are unsure, just pick one and we’ll figure it out.

3.5 Contributing with Patches

To help with Evennia development it’s recommended to do so using a fork repository as described above. But for small, well isolated fixes you are also welcome to submit your suggested Evennia fixes/addendums as a ‘patch‘_.

You can include your patch in an Issue or a Mailing list post. Please avoid pasting the full patch text directly in your post though, best is to use a site like ‘Pastebin‘_ and just supply the link.

3.6 Contributing with Contribs

While Evennia’s core is pretty much game-agnostic, it also has a contrib/ directory. The contrib directory contains game systems that are specialized or useful only to certain types of games. Users are welcome to contribute to the contrib/ directory. Such contributions should always happen via a Forked repository as described above.

• If you are unsure if your idea/code is suitable as a contrib, ask the devs before putting any work into it. This can also be a good idea in order to not duplicate efforts. This can also act as a check that your implementation idea

10 Chapter 3. Contributing Evennia Documentation, Release 0.9

is sound. We are, for example, unlikely to accept contribs that require large modifications of the game directory structure. • If your code is intended primarily as an example or shows a concept/principle rather than a working system, it is probably not suitable for contrib/. You are instead welcome to use it as part of a ‘new tutorial‘_! • The code should ideally be contained within a single Python module. But if the contribution is large this may not be practical and it should instead be grouped in its own subdirectory (not as loose modules). • The contribution should preferably be isolated (only make use of core Evennia) so it can easily be dropped into use. If it does depend on other contribs or third-party modules, these must be clearly documented and part of the installation instructions. • The code itself should follow Evennia’s ‘Code style guidelines‘_. • The code must be well documented as described in our ‘documentation style guide‘_. Expect that your code will be read and should be possible to understand by others. Include comments as well as a header in all modules. If a single file, the header should include info about how to include the contrib in a game (installation instructions). If stored in a subdirectory, this info should go into a new README.md file within that directory. • Within reason, your contribution should be designed as genre-agnostic as possible. Limit the amount of game- style-specific code. Assume your code will be applied to a very different game than you had in mind when creating it. • To make the licensing situation clear we assume all contributions are released with the same ‘license as Even- nia‘_. If this is not possible for some reason, talk to us and we’ll handle it on a case-by-case basis. • Your contribution must be covered by ‘unit tests‘_. Having unit tests will both help make your code more stable and make sure small changes does not break it without it being noticed, it will also help us test its functionality and merge it quicker. If your contribution is a single module, you can add your unit tests to evennia/contribs/tests.py. If your contribution is bigger and in its own sub-directory you could just put the tests in your own tests.py file (Evennia will find it automatically). • Merging of your code into Evennia is not guaranteed. Be ready to receive feedback and to be asked to make corrections or fix bugs. Furthermore, merging a contrib means the Evennia project takes on the responsibility of maintaining and supporting it. For various reasons this may be deemed to be beyond our manpower. However, if your code were to not be accepted for merger for some reason, we will instead add a link to your online repository so people can still find and use your work if they want. Request: https://github.com/evennia/evennia/pulls .. _patch: https://secure.wikimedia.org/wikipedia/en/wiki/Patch_ %28computing%29 .. _Pastebin: http://pastebin.com/ .. _new tutorial: https://github.com/evennia/evennia/wiki/ Tutorials .. _Code style guidelines: https://github.com/evennia/evennia/blob/master/CODING_STYLE.md .. _docu- mentation style guide: https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#doc-strings .. _license as Evennia: Licensing. .. _unit tests: Unit-Testing.html

3.6. Contributing with Contribs 11 Evennia Documentation, Release 0.9

12 Chapter 3. Contributing CHAPTER 4

Soft Code

Softcode is a very simple programming language that was created for in-game development on TinyMUD derivatives such as MUX, PennMUSH, TinyMUSH, and RhostMUSH. The idea is that by providing a stripped down, minimalistic language for in-game use, you can allow quick and easy building and game development to happen without having to learn /C++. There is an added benefit of not having to have to hand out shell access to all developers, and permissions can be used to alleviate many security problems.

Writing and installing softcode is done through a MUD client. Thus it is not a formatted language. Each softcode function is a single line of varying size. Some functions can be a half of a page long or more which is obviously not very readable nor (easily) maintainable over time.

4.1 Examples of Softcode

Here is a simple ‘Hello World!’ command:

@set me=HELLO_WORLD.C:$hello:@pemit %#=Hello World!

Pasting this into a MUX/MUSH and typing ‘hello’ will theoretically yield ‘Hello World!’, assuming certain flags are not set on your account object.

Setting attributes is done via @set. Softcode also allows the use of the (&) . This shorter version looks like this:

13 Evennia Documentation, Release 0.9

&HELLO_WORLD.C me=$hello:@pemit %#=Hello World!

Perhaps I want to break the Hello World into an attribute which is retrieved when emitting:

&HELLO_VALUE.D me=Hello World &HELLO_WORLD.C me=$hello:@pemit %#=[(HELLO_VALUE.D)]

The v() function returns the HELLO_VALUE.D attribute on the object that the command resides (me, which is yourself in this case). This should yield the same output as the first example.

If you are still curious about how Softcode works, take a look at some external resources: • http://www.tinymux.com/wiki/index.php/Softcode • http://www.duh.com/discordia/mushman/man2x1

4.2 Problems with Softcode

Softcode is excellent at what it was intended for: simple things. It is a great tool for making an interactive object, a room with ambiance, simple global commands, simple economies and coded systems. However, once you start to try to write something like a complex combat system or a higher end economy, you’re likely to find yourself buried under a mountain of functions that span multiple objects across your entire code.

Not to mention, softcode is not an inherently fast language. It is not compiled, it is parsed with each calling of a function. While MUX and MUSH parsers have jumped light years ahead of where they once were they can still stutter under the weight of more complex systems if not designed properly.

4.3 Changing Times

Now that starting text-based games is easy and an option for even the most technically inarticulate, new projects are a dime a dozen. People are starting new MUDs every day with varying levels of commitment and ability. Because of this shift from fewer, larger, well-staffed games to a bunch of small, one or two developer games, some of the benefit of softcode fades.

Softcode is great in that it allows a mid to large sized staff all work on the same game without stepping on one another’s toes. As mentioned before, shell access is not necessary to develop a MUX or a MUSH. However, now that we are seeing a lot more small, one or two-man shops, the issue of shell access and stepping on each other’s toes is a lot less.

4.4 Our Solution

Evennia shuns in-game softcode for on-disk Python modules. Python is a popular, mature and

14 Chapter 4. Soft Code Evennia Documentation, Release 0.9

professional programming language. You code it using the conveniences of modern text editors. Evennia developers have access to the entire library of Python modules out there in the wild - not to mention the vast online help resources available. Python code is not bound to one-line functions on objects but complex systems may be organized neatly into real source code modules, sub-modules, or even broken out into entire Python packages as desired.

So what is not included in Evennia is a MUX/MOO-like online player-coding system. Advanced coding in Evennia is primarily intended to be done outside the game, in full-fledged Python modules. Advanced building is best handled by extending Evennia’s command system with your own sophisticated building commands. We feel that with a small development team you are better off using a professional source-control system (svn, git, bazaar, mercurial etc) anyway.

4.5 Your Solution

Adding advanced and flexible building commands to your game is easy and will probably be enough to satisfy most creative builders. However, if you really, really want to offer online coding, there is of course nothing stopping you from adding that to Evennia, no matter our recommendations. You could even re-implement MUX’ softcode in Python should you be very ambitious. The in-game-python is an optional pseudo-softcode plugin aimed at developers wanting to script their game from inside it.

4.5. Your Solution 15 Evennia Documentation, Release 0.9

16 Chapter 4. Soft Code CHAPTER 5

Using MUX as a Standard

Evennia allows for any command syntax. If you like the way , or handle things, you could emulate that with Evennia. If you are ambitious you could even design a whole new style, perfectly fitting your own dreams of the ideal game. We do offer a default however. The default Evennia setup tends to resemble MUX2, and its cousins PennMUSH, TinyMUSH, and RhostMUSH. While the reason for this similarity is partly historical, these codebases offer very mature feature sets for administration and building. Evennia is not a MUX system though. It works very differently in many ways. For example, Evennia deliberately lacks an online softcode language (a policy explained on our softcode policy page). Evennia also does not shy from using its own syntax when deemed appropriate: the MUX syntax has grown organically over a long time and is, frankly, rather arcane in places. All in all the default command syntax should at most be referred to as “MUX-like” or “MUX-inspired”.

5.1 Documentation policy

All the commands in the default command sets should have their doc-strings formatted on a similar form:

""" Short header

Usage: key[/switches, if any] [optional] choice1||choice2||choice3

Switches: switch1 - description switch2 - description

Examples: usage example and output

Longer documentation detailing the command. (continues on next page)

17 Evennia Documentation, Release 0.9

(continued from previous page)

"""

• Two spaces are used for indentation in all default commands. • Square [] surround optional, skippable arguments. • Angled brackets < > surround a description of what to write rather than the exact syntax. • *Explicit choices are separated by |. To avoid this being parsed as a color code, use || (this will come out as a single |) or put spaces around the character (“|”) if there’s plenty of room. • The Switches and Examples blocks are optional based on the Command. Here is the nick command as an example:

""" Define a personal alias/nick

Usage: nick[/switches] = [] alias ''

Switches: object - alias an object account - alias an account clearall - clear all your aliases list - show all defined aliases (also "nicks" works)

Examples: nick hi = say Hello, I' Sarah! nick/object tom = the tall man

A 'nick' is a personal shortcut you create for your own use [...]

"""

For commands that require arguments, the policy is for it to return a Usage: string if the command is entered without any arguments. So for such commands, the Command body should contain something to the effect of if not self.args: self.caller.msg("Usage: nick[/switches] = []") return

18 Chapter 5. Using MUX as a Standard CHAPTER 6

Game Planning

So you have Evennia up and running. You have a great game idea in mind. Now it’s time to start cracking! But where to start? Here are some ideas for a workflow. 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. There is an article in the e-zine which was written by the Evennia lead dev. It focuses more on you finding out your motivations for making a game - you can read the article here. Below are some minimal steps for getting the first version of a new game world going with players. It’s worth to at least make the attempt to do these steps in order even if you are itching to jump ahead in the development cycle. On the other hand, you should also make sure to keep your work fun for you, or motivation will falter. Making a full game is a lot of work as it is, you’ll need all your motivation to make it a reality. Remember that 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. So our first all overshadowing goal is to beat those odds and get something out the door! Even if it’s a scaled-down version of your dream game, lacking many “must-have” features! It’s better to get it out there and expand on it later than to code in isolation forever until you burn out, lose interest or your hard drive crashes. Like is common with online games, getting a game out the door does not mean you are going to be “finished” with the game - most MUDs add features gradually over the course of years - it’s often part of the fun!

6.1 Planning (step 1)

This is what you do before having coded a single line or built a single room. 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. It is by all means very important to define what is the unique appeal of your game. But it’s unfortunately not enough to make your game a reality. To do that you must also have an idea of how to actually map those great ideas onto Evennia. A good start is to begin by planning out the basic primitives of the game and what they need to be able to do. Below are a far-from-complete list of examples (and for your first version you should definitely try for a much shorter list):

19 Evennia Documentation, Release 0.9

6.1.1 Systems

These are the behind-the-scenes features that exist in your game, often without being represented by a specific in-game object. • Should your game rules be enforced by coded systems or are you planning for human game masters to run and arbitrate rules? • What are the actual mechanical game rules? How do you decide if an action succeeds or fails? What “rolls” does the game need to be able to do? Do you base your game off an existing system or make up your own? • Does the flow of time matter in your game - does night and day change? What about seasons? Maybe your magic system is affected by the phase of the moon? • Do you want changing, global weather? This might need to operate in tandem over a large number of rooms. • Do you want a game-wide economy or just a simple barter system? Or no formal economy at all? • Should characters be able to send mail to each other in-game? • Should players be able to post on Bulletin boards? • What is the staff hierarchy in your game? What powers do you want your staff to have? • What should a Builder be able to build and what commands do they need in order to do that? • etc.

6.1.2 Rooms

Consider the most basic room in your game. • Is a simple description enough or should the description be able to change (such as with time, by light conditions, weather or season)? • Should the room have different statuses? Can it have smells, sounds? Can it be affected by dramatic weather, fire or magical effects? If so, how would this affect things in the room? Or are these things something admins/game masters should handle manually? • Can objects be hidden in the room? Can a person hide in the room? How does the room display this? • etc.

6.1.3 Objects

Consider the most basic (non-player-controlled) object in your game. • How numerous are your objects? Do you want large -lists or are objects just role playing props created on demand? • Does the game use money? If so, is each coin a separate object or do you just store a bank account value? • What about multiple identical objects? Do they form stacks and how are those stacks handled in that case? • Does an object have weight or volume (so you cannot carry an infinite amount of them)? • Can objects be broken? If so, does it have a health value? Is burning it causing the same damage as smashing it? Can it be repaired? • Is a weapon a specific type of object or are you supposed to be able to fight with a chair too? Can you fight with a flower or piece of paper as well? • NPCs/mobs are also objects. Should they just stand around or should they have some sort of AI?

20 Chapter 6. Game Planning Evennia Documentation, Release 0.9

• Are NPCs/mobs differet entities? How is an Orc different from a Kobold, in code - are they the same object with different names or completely different types of objects, with custom code? • Should there be NPCs giving quests? If so, how would you track quest status and what happens when multiple players try to do the same quest? Do you use instances or some other mechanism? • etc.

6.1.4 Characters

These are the objects controlled directly by Players. • Can players have more than one Character active at a time or are they allowed to multi-play? • How does a Player create their Character? A Character-creation screen? Answering questions? Filling in a form? • Do you want to use classes (like “Thief”, “Warrior” etc) or some other system, like Skill-based? • 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 skill allows a Character to wield a weapon and hit? Do they need a special skill to wield a chair rather than a sword? • Does a Character need a Strength attribute to tell how much they can carry or which objects they can smash? • What does the skill tree look like? Can a Character gain experience to improve? By killing enemies? Solving quests? By roleplaying? • etc. 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.

6.2 Coding (step 2)

This is the actual work of creating the “game” part of your game. Many “game-designer” types tend to gloss over this bit and jump directly to World Building. Vice versa, many “game-coder” types tend to jump directly to this part without doing the Planning first. Neither way is good and will lead to you having to redo all your hard work at least once, probably more. Evennia’s Developer Central tries to help you with this bit of development. We also have a slew of Tutorials with worked examples. Evennia tries hard to make this part easier for you, but there is no way around the fact that if you want anything but a very basic Talker-type game you will have to bite the bullet and code your game (or find a coder willing to do it for you). Even if you won’t code anything yourself, as a designer you need to at least understand the basic paradigms of Evennia, such as Objects, Commands and Scripts and how they hang together. We recommend you go through the Tutorial World in detail (as well as glancing at its code) to get at least a feel for what is involved behind the scenes. You could also look through the tutorial for building a game from scratch. 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.

6.2. Coding (step 2) 21 Evennia Documentation, Release 0.9

A good idea is to host your code online (publicly or privately) using version control. Not only will this make it easy for multiple coders to collaborate (and have a bug-tracker etc), it also means your work is backed up at all times. The Version Control tutorial has instructions for setting up a sane developer environment with proper version control.

6.2.1 “Tech Demo” Building

This is an integral part of your Coding. It might seem obvious to experienced coders, but it cannot be emphasized enough that you should test things on a small scale before putting your untested code into a large game-world. The earlier you test, the easier and cheaper it will be to fix bugs and even rework things that didn’t work out the way you thought they would. You might even have to go back to the Planning phase if your ideas can’t handle their meet with reality. This means building singular in-game examples. Make one room and one object of each important type and test so they work correctly in isolation. Then add more if they are supposed to interact with each other in some way. Build a small series of rooms to test how mobs move around . . . and so on. In short, a test-bed for your growing code. It should be done gradually until you have a fully functioning (if not guaranteed bug-free) miniature tech demo that shows all the features you want in the first release of your game. There does not need to be any game play or even a theme to your tests, this is only for you and your co-coders to see. The more testing you do on this small scale, the less headaches you will have in the next phase.

6.3 World Building (step 3)

Up until this point we’ve only had a few tech-demo objects in the database. This step is the act of populating the database with a larger, thematic world. Too many would-be developers jump to this 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. And whereas Evennia’s typeclass system does allow you to edit the properties of existing objects, some hooks are only called at object creation . . . Suffice to say you are 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. Before starting to build, you should also plan ahead again. 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 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 have made at this point. Since Building often involves different people than those Coding, you also get a chance to hear if some things are hard to understand or non-intuitive. Make sure to respond to this feedback.

6.4 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! 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). Follow the quick instructions for Online Setup to make your game visible online. If you hadn’t already, make sure to

22 Chapter 6. Game Planning Evennia Documentation, Release 0.9 put up your game on the Evennia game index so people know it’s in the works (actually, even pre-alpha games are allowed in the index so don’t be shy)!

6.5 Beta Release/Perpetual Beta

Once things stabilize in Alpha you can move to Beta and let more people in. Many MUDs are in perpetual beta, meaning they are never considered “finished”, but just repeat the cycle of Planning, Coding, Testing and Building over and over as new features get implemented or Players come with suggestions. As the game designer it is now up to you to gradually perfect your vision.

6.6 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!

6.5. Beta Release/Perpetual Beta 23 Evennia Documentation, Release 0.9

24 Chapter 6. Game Planning CHAPTER 7

Installation and setup

This chapter helps with installing and managing the server itself as well as figuring out where things go.

7.1 Getting Started

This will help you download, install and start Evennia for the first time. Note: You don’t need to make anything visible to the ’net in order to run and test out Evennia. Apart from downloading and updating you don’t even need an connection until you feel ready to share your game with the world. • Quick Start • Requirements • Install • Mac Install • Windows Install • Running in Docker • Where to Go Next • Troubleshooting • Glossary of terms

7.1.1 Quick Start

For the impatient. If you have trouble with a step, you should jump on to the more detailed instructions for your platform.

1. Install Python, GIT and python-virtualenv. Start a Console/Terminal.

25 Evennia Documentation, Release 0.9

2. cd to some place you want to do your development (like a folder /home/anna/muddev/ on Linux or a folder in your personal user directory on Windows). 3. git clone https://github.com/evennia/evennia.git 4. virtualenv evenv 5. source evenv/bin/activate (Linux, Mac), evenv\Scripts\activate (Windows) 6. pip install -e evennia 7. evennia --init mygame 8. cd mygame 9. evennia migrate 10. evennia start (make sure to make a superuser when asked) Evennia should now be running and you can connect to it by pointing a to http://localhost:4001 or a MUD telnet client to localhost:4000 (use 127.0.0.1 if your does not recognize localhost).

We also release Docker images based on master and develop branches.

7.1.2 Requirements

Any system that supports Python3.7+ should work. We’ll describe how to install everything in the following sections.

• Linux/Unix • Windows (Vista, Win7, Win8, Win10) • Mac OSX (>=10.5 recommended) • Python (v3.7, 3.8 are tested) • virtualenv for making isolated Python environments. Installed with pip install virtualenv. • GIT - version control software for getting and updating Evennia itself - Mac users can use the git-osx-installer or the MacPorts version. • (v19.0+) • ZopeInterface (v3.0+) - usually included in Twisted packages • Linux/Mac users may need the gcc and python-dev packages or equivalent. • Windows users need MS Visual C++ and maybe pypiwin32. • Django (v2.2.), be warned that latest dev version is usually untested with Evennia)

7.1.3 Linux Install

If you run into any issues during the installation and first start, please check out Linux Troubleshooting.

26 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

For Debian-derived systems (like Ubuntu, Mint etc), start a terminal and install the dependencies:

sudo apt-get update sudo apt-get install python3 python3-pip python3-dev python3-setuptools python3-git

˓→python3-virtualenv gcc

# If you are using an Ubuntu version that defaults to Python3, like 18.04+, use this

˓→instead: sudo apt-get update sudo apt-get install python3.7 python3-pip python3.7-dev python3-setuptools

˓→virtualenv gcc

Note that, the default Python version for your distribution may still not be Python3.7 after this. This is ok - we’ll specify exactly which Python to use later. You should make sure to not be root after this step, running as root is a security risk. Now create a folder where you want to do all your Evennia development:

mkdir muddev cd muddev

Next we fetch Evennia itself: git clone https://github.com/evennia/evennia.git

A new folder evennia will appear containing the Evennia library. This only contains the source code though, it is not installed yet. To isolate the Evennia install and its dependencies from the rest of the system, it is good Python practice to install into a virtualenv. If you are unsure about what a virtualenv is and why it’s useful, see the ‘Glossary entry on virtualenv‘_.

Run python -V to see which version of Python your system defaults to.

# If your Linux defaults to Python3.7+: virtualenv evenv

# If your Linux defaults to Python2 or an older version # of Python3, you must instead point to Python3.7+ explicitly: virtualenv-p/usr/bin/python3.7 evenv

A new folder evenv will appear (we could have called it anything). This folder will hold a self-contained setup of Python packages without interfering with default Python packages on your system (or the Linux distro lagging behind on Python package versions). It will also always use the right version of Python. Activate the virtualenv:

7.1. Getting Started 27 Evennia Documentation, Release 0.9

source evenv/bin/activate

The text (evenv) should appear next to your prompt to show that the virtual environment is active.

Remember that you need to activate the virtualenv like this every time you start a new terminal to get access to the Python packages (notably the important evennia program) we are about to install.

Next, install Evennia into your active virtualenv. Make sure you are standing at the top of your mud directory tree (so you see the evennia/ and evenv/ folders) and run

pip install-e evennia

For more info about pip, see the ‘Glossary entry on pip‘_. If install failed with any issues, see Linux Troubleshooting.

Next we’ll start our new game, here called “mygame”. This will create yet another new folder where you will be creating your new game:

evennia--init mygame

Your final folder structure should look like this:

./muddev evenv/ evennia/ mygame/

You can ‘configure Evennia‘_ extensively, for example to use a ‘different database‘_. For now we’ll just stick to the defaults though.

cd mygame evennia migrate # (this creates the database) evennia start # (create a superuser when asked. is optional.)

| Server logs are found in ``mygame/server/logs/``. To easily view server logs | live in the terminal, use ``evennia -`` (exit the log-view with Ctrl-C).

28 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

Your game should now be running! Open a web browser at http://localhost:4001 or point a telnet client to localhost:4000 and log in with the user you created. Check out where to go next.

7.1.4 Mac Install

The Evennia server is a terminal program. Open the terminal e.. from Applications->Utilities->Terminal. ‘Here is an introduction to the Mac terminal‘_ if you are unsure how it works. If you run into any issues during the installation, please check out Mac Troubleshooting.

• Python should already be installed but you must make sure it’s a high enough version. (‘This‘_ discusses how you may upgrade it). Remember that you need Python3.7, not Python2.7! • GIT can be obtained with git-osx-installer or via MacPorts ‘as described here‘_. • If you run into issues with installing Twisted later you may need to install gcc and the Python headers. After this point you should not need sudo or any higher privileges to install anything. Now create a folder where you want to do all your Evennia development:

mkdir muddev cd muddev

Next we fetch Evennia itself:

git clone https://github.com/evennia/evennia.git

A new folder evennia will appear containing the Evennia library. This only contains the source code though, it is not installed yet. To isolate the Evennia install and its dependencies from the rest of the system, it is good Python practice to install into a virtualenv. If you are unsure about what a virtualenv is and why it’s useful, see the ‘Glossary entry on virtualenv‘_.

Run python -V to check which Python your system defaults to.

# If your Mac defaults to Python3: virtualenv evenv

# If your Mac defaults to Python2 you need to specify the Python3.7 binary explicitly: virtualenv-p/path/to/your/python3.7 evenv

A new folder evenv will appear (we could have called it anything). This folder will hold a self-contained setup of Python packages without interfering with default Python packages on your system. Activate the virtualenv:

7.1. Getting Started 29 Evennia Documentation, Release 0.9

source evenv/bin/activate

The text (evenv) should appear next to your prompt to show the virtual environment is active.

Remember that you need to activate the virtualenv like this every time you start a new terminal to get access to the Python packages (notably the important evennia program) we are about to install.

Next, install Evennia into your active virtualenv. Make sure you are standing at the top of your mud directory tree (so you see the evennia/ and evenv/ folders) and run

pip install--upgrade pip # Old pip versions may be an issue on Mac. pip install--upgrade setuptools # Ditto concerning Mac issues. pip install-e evennia

For more info about pip, see the ‘Glossary entry on pip‘_. If install failed with any issues, see Mac Troubleshooting.

Next we’ll start our new game. We’ll call it “mygame” here. This creates a new folder where you will be creating your new game:

evennia--init mygame

Your final folder structure should look like this:

./muddev evenv/ evennia/ mygame/

You can ‘configure Evennia‘_ extensively, for example to use a ‘different database‘_. We’ll go with the defaults here.

cd mygame evennia migrate # (this creates the database) evennia start # (create a superuser when asked. Email is optional.)

| Server logs are found in ``mygame/server/logs/``. To easily view server logs (continues on next page)

30 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

(continued from previous page) | live in the terminal, use ``evennia -l`` (exit the log-view with Ctrl-C).

Your game should now be running! Open a web browser at http://localhost:4001 or point a telnet client to localhost:4000 and log in with the user you created. Check out where to go next.

7.1.5 Windows Install

If you run into any issues during the installation, please check out Windows Troubleshooting.

If you are running Windows10, consider using the Windows Subsystem for Linux (‘WSL‘_) instead. You should then follow the Linux install instructions above.

The Evennia server itself is a command line program. In the Windows launch menu, start All Programs -> Accessories -> command prompt and you will get the Windows command line interface. Here is ‘one of many tutorials on using the Windows command line‘_ if you are unfamiliar with it.

• Install Python ‘from the Python homepage‘_. You will need to be a Windows Administrator to install packages. You want Python version 3.7.0 (latest verified version), usually the 64-bit version (although it doesn’t matter too much). When installing, make sure to check-mark *all* install options, especially the one about making Python available on the path (you may have to scroll to see it). This allows you to just write python in any console without first finding where the python program actually sits on your hard drive. • You need to also get GIT and install it. You can use the default install options but when you get asked to “Adjust your PATH environment”, you should select the second option “Use Git from the Windows Command Prompt”, which gives you more freedom as to where you can use the program. • Finally you must install the ‘Microsoft Visual C++ compiler for Python‘_. Download and run the linked installer and install the C++ tools. Keep all the defaults. Allow the install of the “Win10 SDK”, even if you are on Win7 (not tested on older Windows versions). If you later have issues with installing Evennia due to a failure to build the “Twisted wheels”, this is where you are missing things. • You may need the pypiwin32 Python headers. Install these only if you have issues.

You can install Evennia wherever you want. cd to that location and create a new folder for all your Evennia development (let’s call it muddev).

mkdir muddev cd muddev

| Hint: If ``cd`` isn’t working you can use ``pushd`` instead to (continues on next page)

7.1. Getting Started 31 Evennia Documentation, Release 0.9

(continued from previous page) force the | directory change.

Next we fetch Evennia itself:

git clone https://github.com/evennia/evennia.git

A new folder evennia will appear containing the Evennia library. This only contains the source code though, it is not installed yet. To isolate the Evennia install and its dependencies from the rest of the system, it is good Python practice to install into a virtualenv. If you are unsure about what a virtualenv is and why it’s useful, see the ‘Glossary entry on virtualenv‘_.

In your console, try python -V to see which version of Python your system defaults to.

pip install virtualenv

# If your setup defaults to Python3.7: virtualenv evenv

# If your setup defaults to Python2, specify path to python3.exe explicitly: virtualenv-p C:\Python37\python.exe evenv

# If you get an infinite spooling response, press CTRL + C to interrupt and try using: python-m venv evenv

A new folder evenv will appear (we could have called it anything). This folder will hold a self-contained setup of Python packages without interfering with default Python packages on your system. Activate the virtualenv:

# If you are using a standard command prompt, you can use the following: evenv\scripts\activate.bat

# If you are using a Shell, Git Bash, or other, you can use the following: .\evenv\scripts\activate

The text (evenv) should appear next to your prompt to show the virtual environment is active.

Remember that you need to activate the virtualenv like this every time you start a new console window if you want to get access to the Python packages (notably the important evennia program) we are about to install.

32 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

Next, install Evennia into your active virtualenv. Make sure you are standing at the top of your mud directory tree (so you see the evennia and evenv folders when you use the dir command) and run

pip install-e evennia

For more info about pip, see the ‘Glossary entry on pip‘_. If the install failed with any issues, see Windows Troubleshooting. Next we’ll start our new game, we’ll call it “mygame” here. This creates a new folder where you will be creating your new game:

evennia--init mygame

Your final folder structure should look like this:

path\to\muddev evenv\ evennia\ mygame\

You can ‘configure Evennia‘_ extensively, for example to use a ‘different database‘_. We’ll go with the defaults here.

cd mygame evennia migrate # (this creates the database) evennia start # (create a superuser when asked. Email is optional.)

| Server logs are found in ``mygame/server/logs/``. To easily view server logs | live in the terminal, use ``evennia -l`` (exit the log-view with Ctrl-C).

Your game should now be running! Open a web browser at http://localhost:4001 or point a telnet client to localhost:4000 and log in with the user you created. Check out where to go next.

7.1.6 Where to Go Next

Welcome to Evennia! Your new game is fully functioning, but empty. If you just logged in, stand in the Limbo room and run

7.1. Getting Started 33 Evennia Documentation, Release 0.9

@batchcommand tutorial_world.build to build ‘Evennia’s tutorial world‘_ - it’s a small solo quest to explore. Only run the instructed @batchcommand once. You’ll get a lot of text scrolling by as the tutorial is built. Once done, the tutorial exit will have appeared out of Limbo - just write tutorial to enter it. Once you get back to Limbo from the tutorial (if you get stuck in the tutorial quest you can do @tel #2 to jump to Limbo), a good idea is to learn how to ‘start, stop and reload‘_ the Evennia server. You may also want to familiarize yourself with some ‘commonly used terms in our Glossary‘_. After that, why not experiment with ‘creating some new items and build some new rooms‘_ out from Limbo. From here on, you could move on to do one of our ‘introductory tutorials‘_ or simply dive headlong into Evennia’s comprehensive ‘manual‘_. While Evennia has no major game systems out of the box, we do supply a range of optional contribs that you can use or borrow from. They range from dice rolling and alternative color schemes to barter and combat systems. You can find the ‘growing list of contribs here‘_. If you have any questions, you can always ask in ‘the developer chat‘_ #evennia on irc.freenode.net or by posting to the ‘Evennia forums‘_. You can also join the ‘Discord Server‘_.

Finally, if you are itching to help out or support Evennia (awesome!) have an issue to report or a feature to request, ‘see here‘_.

Enjoy your stay!

7.1.7 Troubleshooting

If you have issues with installing or starting Evennia for the first time, check the section for your below. If you have an issue not covered here, ‘please report it‘_ so it can be fixed or a workaround found!

Remember, the server logs are in mygame/server/logs/. To easily view server logs in the terminal, you can run evennia -l, or (in the future) start the server with evennia start -l.

Linux Troubleshooting

• If you get an error when installing Evennia (especially with lines mentioning failing to include Python.) then try sudo apt-get install python3-setuptools python3-dev. Once installed, run pip install -e evennia again. • Under some not-updated Linux distributions you may run into errors with a too-old setuptools or missing functools. If so, update your environment with pip install --upgrade pip wheel setuptools. Then try pip install -e evennia again. • One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/ twisted/ with errors like distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('incremental>=16.10.1'). This appears possible to solve by simply updating Ubuntu with sudo apt-get update && sudo apt-get dist-upgrade.

34 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

• Users of Fedora (notably Fedora 24) has reported a gcc error saying the directory /usr/lib/rpm/ redhat/redhat-hardened-cc1 is missing, despite gcc itself being installed. ‘The confirmed work- around‘_ seems to be to install the redhat-rpm-config package with e.g. sudo dnf install redhat-rpm-config. • Some users trying to set up a virtualenv on an NTFS filesystem find that it fails due to issues with symlinks not being supported. Answer is to not use NTFS (seriously, why would you do that to yourself?)

Mac Troubleshooting

• Mac users have reported a critical MemoryError when trying to start Evennia on Mac with a Python version below 2.7.12. If you get this error, update to the latest XCode and Python2 version. • Some Mac users have reported not being able to connect to localhost (i.e. your own computer). If so, try to connect to 127.0.0.1 instead, which is the same thing. Use port 4000 from mud clients and port 4001 from the web browser as usual.

Windows Troubleshooting

• If you installed Python but the python command is not available (even in a new console), then you might have missed installing Python on the path. In the Windows Python installer you get a list of options for what to install. Most or all options are pre-checked except this one, and you may even have to scroll down to see it. Reinstall Python and make sure it’s checked. • If your MUD client cannot connect to localhost:4000, try the equivalent 127.0.0.1:4000 instead. Some MUD clients on Windows does not appear to understand the alias localhost. • If you run virtualenv evenv and get a 'virtualenv' is not recognized as an internal or external command, operable program or batch file. error, you can mkdir evenv, cd evenv and then python -m virtualenv . as a workaround. • Some Windows users get an error installing the Twisted ‘wheel’. A wheel is a pre-compiled binary package for Python. A common reason for this error is that you are using a 32-bit version of Python, but Twisted has not yet uploaded the latest 32-bit wheel. Easiest way to fix this is to install a slightly older Twisted version. So if, say, version 18.1 failed, install 18.0 manually with pip install twisted==18.0. Alternatively you could try to get a 64-bit version of Python (uninstall the 32bit one). If so, you must then deactivate the virtualenv, delete the evenv folder and recreate it anew (it will then use the new Python executable). • If your server won’t start, with no error messages (and no log files at all when starting from scratch), try to start with evennia ipstart instead. If you then see an error about system cannot find the path specified, it may be that the file evennia/evennia/server/twistd.bat has the wrong path to the twistd executable. This file is auto-generated, so try to delete it and then run evennia start to rebuild it and see if it works. If it still doesn’t work you need to open it in a text editor like Notepad. It’s just one line containing the path to the twistd.exe executable as determined by Evennia. If you installed Twisted in a non-standard location this might be wrong and you should update the line to the real location. virtualenv: Glossary#virtualenv .. _Glossary entry on pip: Glossary#pip .. _configure Evennia: Server-Conf#settings- file .. _different database: Choosing-An-SQL-Server.html .. _where to go next: #where-to-go-next .. _Here is an introduction to the Mac terminal: http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line.. _Mac Troubleshooting: #mac-troubleshooting .. _This: http://docs.python-guide.org/en/latest/starting/install/osx/ .. _as described here: http://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac .. _Glossary entry on virtualenv: Glossary#virtualenv .. _Windows Troubleshooting: #windows-troubleshooting .. _WSL: https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux .. _one of many tutorials on using the Windows command line: http://www.bleepingcomputer.com/tutorials/windows-command-prompt-introduction/ .. _from the Python homepage: https://www.python.org/downloads/windows/ .. _Microsoft Visual C++ compiler for Python: https://aka.ms/vs/16/release/vs_buildtools.exe .. _Evennia’s tutorial world: Tutorial-World-Introduction.html .. _start,

7.1. Getting Started 35 Evennia Documentation, Release 0.9 stop and reload: Start-Stop-Reload.html .. _commonly used terms in our Glossary: Glossary.html .. _creating some new items and build some new rooms: https://github.com/evennia/evennia/wiki/Building-Quickstart .. _introduc- tory tutorials: Tutorials.html .. _manual: https://github.com/evennia/evennia/wiki .. _growing list of contribs here: https://github.com/evennia/evennia/blob/master/evennia/contrib/README.md .. _the developer chat: http://webchat. freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb .. _Evennia forums: https://groups.google.com/forum/#%21forum/evennia .. _Discord Server: https://discord.gg/NecFePw .. _see here: how-to-get-and-give-help .. _please report it: https://github.com/evennia/evennia/issues .. _The confirmed work- around: https://gist.github.com/yograterol/99c8e123afecc828cb8c

7.2 Start Stop Reload

You control Evennia from your game folder (we refer to it as mygame/ here), using the evennia program. If the evennia program is not available on the command line you must first install Evennia as described in the Getting Started page.

Hint: If you ever try the evennia command and get an error complaining that the command is not available, make sure your virtualenv is active. Below are described the various management options. Run evennia-h to give you a brief help and evennia menu to give you a menu with options.

7.2.1 Starting Evennia

Evennia consists of two components, the Evennia Server and Portal. Briefly, the Server is what is running the mud. It handles all game-specific things but doesn’t care exactly how players connect, only that they have. The Portal is a gateway to which players connect. It knows everything about telnet, ssh, webclient protocols etc but very little about the game. Both are required for a functioning mud.

evennia start

The above command will start the Portal, which in turn will boot up the Server. The command will print a summary of the process and unless there is an error you will see no further output. Both components will instead log to log files in mygame/server/logs/. For convenience you can follow those logs directly in your terminal by attaching -l to commands:

evennia-l

Will start following the logs of an already running server. When starting Evennia you can also do

36 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

evennia start -l

To stop viewing the log files, press ``Ctrl-C``.

7.2.2 Foreground mode

Normally, Evennia runs as a ‘daemon’, in the background. If you want you can start either of the processes (but not both) as foreground processes in interactive mode. This means they will log directly to the terminal (rather than to log files that we then echo to the terminal) and you can kill the process (not just the log-file view) with Ctrl-C.

evennia istart

will start/restart the Server in interactive mode. This is required if you want to run a debugger. Next time you reload the server, it will return to normal mode.

evennia ipstart

will start the Portal in interactive mode. This is usually only necessary if you want to run Evennia under the control of some other type of process.

7.2.3 Reloading

The act of reloading means the Portal will tell the Server to shut down and then boot it back up again. Everyone will get a message and the game will be briefly paused for all accounts as the server reboots. Since they are connected to the Portal, their connections are not lost.

Reloading is as close to a “warm reboot” you can get. It reinitializes all code of Evennia, but doesn’t kill “persistent” Scripts. It also calls at_server_reload() hooks on all objects so you can save eventual temporary properties you want.

From in-game the @reload command is used. You can also reload the server from outside the game: evennia reload

Sometimes reloading from “the outside” is necessary in case you have added some sort of bug that blocks in-game input.

7.2. Start Stop Reload 37 Evennia Documentation, Release 0.9

7.2.4 Resetting

Resetting is the equivalent of a “cold reboot” - the Server will shut down and then restarted again, but will behave as if it was fully shut down. As opposed to a “real” shutdown, no accounts will be disconnected during a reset. A reset will however purge all non-persistent scripts and will call at_server_shutdown() hooks. It can be a good way to clean unsafe scripts during development, for example.

From in-game the @reset command is used. From the terminal: evennia reset

7.2.5 Rebooting

This will shut down both Server and Portal, which means all connected players will loose their connection. It can only be initiated from the terminal:

evennia reboot

This is identical to doing these two commands: evennia stop evennia start

7.2.6 Shutting down

A full shutdown closes Evennia completely, both Server and Portal. All accounts will be booted and systems saved and turned off cleanly.

From inside the game you initiate a shutdown with the @shutdown command. From command line you do evennia stop

You will see messages of both Server and Portal closing down. All accounts will see the shutdown message and then be disconnected. The same effect happens if you press Ctrl+C while the server runs in interactive mode.

7.2.7 Status and info

To check basic Evennia settings, such as which ports and services are active, this will repeat the initial return given when starting the server:

evennia info

38 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

You can also get a briefer run-status from both components with this command

evennia status

This can be useful for automating checks to make sure the game is running and is responding.

7.2.8 Killing (Linux/Mac only)

In the extreme case that neither of the server processes locks up and does not respond to commands, you can send them kill-signals to force them to shut down. To kill only the Server:

evennia skill

To kill both Server and Portal:

evennia kill

Note that this functionality is not supported on Windows.

7.2.9 Django options

The evennia program will also pass-through options used by the django-admin. These operate on the database in various ways.

evennia migrate # migrate the database evennia shell # launch an interactive, django-aware python shell evennia dbshell # launch database shell

For (many) more options, see the django-admin docs.

7.2.10 Advanced handling of Evennia processes

If you should need to manually manage Evennia’s processors (or view them in a task manager program such as Linux’ top or the more advanced htop), you will find the following processes to be related to Evennia:

•1x twistd ... evennia/server/portal/portal.py - this is the Portal process. •3x twistd ... server.py - One of these processes manages Evennia’s Server component, the main game. The other processes (with the same name but different process id) handle’s Evennia’s internal web server threads. You can look at mygame/server/server.pid to determine which is the main process.

Syntax errors during live development

During development, you will usually modify code and then reload the server to see your changes. This is done by Evennia re-importing your custom modules from disk. Usually bugs in a module will just have you see a traceback in the game, in the log or on the command line. For some really serious syntax errors though, your module might not even be recognized as valid Python. Evennia may then fail to restart correctly.

7.2. Start Stop Reload 39 Evennia Documentation, Release 0.9

From inside the game you see a text about the Server restarting followed by an ever growing list of “. . . ”. Usually this only lasts a very short time (up to a few seconds). If it seems to go on, it means the Portal is still running (you are still connected to the game) but the Server-component of Evennia failed to restart (that is, it remains in a shut-down state). Look at your log files or terminal to see what the problem is - you will usually see a clear traceback showing what went wrong.

Fix your bug then run evennia start

Assuming the bug was fixed, this will start the Server manually (while not restarting the Portal). In-game you should now get the message that the Server has successfully restarted.

7.3 Choosing An SQL Server

This page gives an overview of the supported SQL databases as well as instructions on install: • SQLite3 (default) • PostgreSQL • MySQL / MariaDB Since Evennia uses Django, most of our notes are based off of what we know from the community and their documen- tation. While the information below may be useful, you can always find the most up-to-date and “correct” information at Django’s Notes about supported Databases page.

7.3.1 SQLite3

SQLite3 is a light weight single-file database. It is our default database and Evennia will set this up for you automat- ically if you give no other options. SQLite stores the database in a single file (mygame/server/evennia.db3). This means it’s very easy to reset this database - just delete (or move) that evennia.db3 file and run evennia migrate again! No server process is needed and the administrative overhead and resource consumption is tiny. It is also very fast since it’s run in-memory. For the vast majority of Evennia installs it will probably be all that’s ever needed. SQLite will generally be much faster than MySQL/PostgreSQL but its performance comes with two drawbacks: • SQLite ignores length constraints by design; it is possible to store very large strings and numbers in fields that technically should not accept them. This is not something you will notice; your game will read and write them and function normally, but this can create some data migration problems requiring careful thought if you do need to change databases later. • SQLite can scale well to storage of millions of objects, but if you end up with a thundering herd of users trying to access your MUD and web site at the same time, or you find yourself writing long-running functions to update large numbers of objects on a live game, either will yield errors and interference. SQLite does not work reliably with multiple concurrent threads or processes accessing its records. This has to do with file-locking clashes of the database file. So for a production server making heavy use of process- or thread pools (or when using a third-party webserver like Apache), a proper database is a more appropriate choice.

40 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

Install of SQlite3

This is installed and configured as part of Evennia. The database file is created as mygame/server/evennia.db3 when you run

evennia migrate

without changing any database options. An optional requirement is the sqlite3 client program - this is required if you want to inspect the database data manually. A shortcut for using it with the evennia database is evennia dbshell. Linux users should look for the sqlite3 package for their distro while Mac/Windows should get the sqlite-tools package from this page. To inspect the default Evennia database (once it’s been created), go to your game dir and do

sqlite3 server/evennia.db3 # or evennia dbshell

This will bring you into the sqlite command line. Use .help for instructions and .quit to exit. See ‘here‘_ for a cheat-sheet of commands.

7.3.2 PostgreSQL

PostgreSQL is an open-source database engine, recommended by Django. While not as fast as SQLite for normal usage, it will scale better than SQLite, especially if your game has an very large database and/or extensive web presence through a separate server process.

Install and initial setup of PostgreSQL

First, install the posgresql server. Version 9.6 is tested with Evennia. Packages are readily available for all distri- butions. You need to also get the psql client (this is called postgresql-client on debian-derived systems). Windows/Mac users can find what they need on the postgresql download page. You should be setting up a password for your database-superuser (always called postgres) when you install. For interaction with Evennia you need to also install psycopg2 to your Evennia install (pip install psycopg2-binary in your virtualenv). This acts as the python bridge to the database server. Next, start the postgres client:

psql -U postgres --password

| **Warning:** With the ``--password`` argument, Postgres should prompt you for a password. | If it won’t, replace that with ``-p yourpassword`` instead. Do not use the ``-p`` argument unless you have to since the resulting command, and your password, will be logged in the shell history.

This will open a console to the postgres service using the psql client. On the psql command line:

CREATE USER evennia WITH PASSWORD'somepassword'; CREATE DATABASE evennia;

# Postgres-specific optimizations # https://docs.djangoproject.com/en/dev/ref/databases/#optimizing-postgresql-s- ˓→configuration (continues on next page)

7.3. Choosing An SQL Server 41 Evennia Documentation, Release 0.9

(continued from previous page) ALTER ROLE evennia SET client_encoding TO'utf8'; ALTER ROLE evennia SET default_transaction_isolation TO'read committed'; ALTER ROLE evennia SET timezone TO'UTC';

GRANT ALL PRIVILEGES ON DATABASE evennia TO evennia; \l # list all databases and permissions \ # exit

‘Here‘_ is a cheat-sheet for psql commands.

We create a database user ‘evennia’ and a new database named evennia (you can call them whatever you want though). We then grant the ‘evennia’ user full privileges to the new database so it can read/write etc to it. If you in the future wanted to completely wipe the database, an easy way to do is to log in as the postgres superuser again, then do DROP DATABASE evennia;, then CREATE and GRANT steps above again to recreate the database and grant privileges.

Evennia PostgreSQL configuration

Edit ‘mygame/server/conf/secret_settings.py and add the following section:

# # PostgreSQL Database Configuration # DATABASES={ 'default':{ 'ENGINE':'django.db.backends.postgresql_psycopg2', 'NAME':'evennia', 'USER':'evennia', 'PASSWORD':'somepassword', 'HOST':'localhost', 'PORT':'' # use default }}

If you used some other name for the database and user, enter those instead. Run evennia migrate to populate your database. Should you ever want to inspect the database directly you can from now on also use evennia dbshell as a shortcut to get into the postgres command line for the right database and user. With the database setup you should now be able to start start Evennia normally with your new database.

7.3.3 MySQL / MariaDB

MySQL is a commonly used proprietary database system, on par with PostgreSQL. There is an open-source alternative called MariaDB that mimics all functionality and command syntax of the former. So this section covers both.

42 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

Installing and initial setup of MySQL/MariaDB

First, install and setup MariaDB or MySQL for your specific server. Linux users should look for the mysql-server or mariadb-server packages for their respective distributions. Windows/Mac users will find what they need from the MySQL downloads or MariaDB downloads pages. You also need the respective database clients (mysql, mariadb-client), so you can setup the database itself. When you install the server you should usually be asked to set up the database root user and password. You will finally also need a Python interface to allow Evennia to talk to the database. Django recommends the mysqlclient one. Install this into the evennia virtualenv with pip install mysqlclient. Start the database client (this is named the same for both mysql and mariadb): mysql-u root-p

You should get to enter your database root password (set this up when you installed the database server). Inside the database client interface:

CREATE USER 'evennia'@'localhost' IDENTIFIED BY 'somepassword'; CREATE DATABASE evennia; ALTER DATABASE `evennia` CHARACTER SET utf8; # note that it's `evennia` not

˓→'evennia'! GRANT ALL PRIVILEGES ON evennia.* TO 'evennia'@'localhost'; FLUSH PRIVILEGES; exit

Here is a mysql command cheat sheet. Above we created a new local user and database (we called both ‘evennia’ here, you can name them what you prefer). We set the character set to utf8 to avoid an issue with prefix character length that can pop up on some installs otherwise. Next we grant the ‘evennia’ user all privileges on the evennia database and make sure the privileges are applied. Exiting the client brings us back to the normal terminal/console. Note: If you are not using MySQL for anything else you might consider granting the ‘evennia’ user full privileges with GRANT ALL PRIVILEGES ON *.* TO 'evennia'@'localhost';. If you do, it means you can use evennia dbshell later to connect to mysql, drop your database and re- create it as a way of easy reset. Without this extra privilege you will be able to drop the database but not re-create it without first switching to the database-root user.

7.3.4 Add MySQL configuration to Evennia

To tell Evennia to use your new database you need to edit mygame/server/conf/settings.py (or secret_settings.py if you don’t want your db info passed around on git repositories). Note: The Django documentation suggests using an external db.cnf or other external conf-formatted file. Evennia users have however found that this leads to problems (see e.g. issue #1184). To avoid trouble we recommend you simply put the configuration in your settings as below.

# # MySQL Database Configuration # DATABASES={ 'default':{ 'ENGINE':'django.db.backends.mysql', 'NAME':'evennia', 'USER':'evennia', (continues on next page)

7.3. Choosing An SQL Server 43 Evennia Documentation, Release 0.9

(continued from previous page) 'PASSWORD':'somepassword', 'HOST':'localhost', # or an IP Address that your DB is hosted on 'PORT':'', # use default port } }

Change this to fit your database setup. Next, run: evennia migrate to populate your database. Should you ever want to inspect the database directly you can from now on also use evennia dbshell as a shortcut to get into the postgres command line for the right database and user. With the database setup you should now be able to start start Evennia normally with your new database.

7.3.5 Others

No testing has been performed with Oracle, but it is also supported through Django. There are community maintained drivers for MS SQL and possibly a few others. If you try other databases out, consider expanding this page with instructions.

7.4 Apache Config

Warning: This information is presented as a convenience, using another webserver than Evennia’s own is not directly supported and you are on your own if you want to do so. Evennia’s webserver works out of the box without any extra configuration and also runs in-process making sure to avoid caching race conditions. The browser web client will most likely not work (at least not without tweaking) on a third-party web server.

One reason for wanting to use an external webserver like Apache would be to act as a proxy in front of the Evennia webserver. Getting this working with TLS (encryption) requires some extra work covered at the end of this page.

Note that the Apache instructions below might be outdated. If something is not working right, or you use Evennia with a different server, please let us know. Also, if there is a particular Linux distro you would like covered, please let us know.

7.4.1 mod_wsgi Setup

Install mod_wsgi

Fedora/RHEL

Apache HTTP Server and mod_wsgi are available in the standard package repositories for Fedora and RHEL:

44 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

# dnf install httpd mod_wsgi or # yum install httpd mod_wsgi

Ubuntu/Debian

Apache HTTP Server and mod_wsgi are available in the standard package repositories for Ubuntu and Debian:

# apt-get update # apt-get install apache2 libapache2-mod-wsgi

Copy and modify the VHOST

After mod_wsgi is installed, copy the evennia/web/utils/evennia_wsgi_apache.conf file to your apache2 vhosts/sites folder. On Debian/Ubuntu, this is /etc/apache2/sites-enabled/. Make your modifications after copying the file there.

Read the comments and change the paths to point to the appropriate locations within your setup.

Restart/Reload Apache

You’ll then want to reload or restart apache2 after changing the configurations.

Fedora/RHEL/Ubuntu

# systemctl restart httpd

Ubuntu/Debian

# systemctl restart apache2

Enjoy

With any luck, you’ll be able to point your browser at your domain or subdomain that you set up in your vhost and see the nifty default Evennia webpage. If not, read the hopefully informative error message and work from there. Questions may be directed to our ‘Evennia Community site‘_.

A note on code reloading

If your mod_wsgi is set up to run on daemon mode (as will be the case by default on Debian and Ubuntu), you may tell mod_wsgi to reload by using the touch command on evennia/game/web/utils/apache_wsgi.conf. When mod_wsgi sees that the file modification time has changed, it will force a code reload. Any modifications to the code will not be propagated to the

7.4. Apache Config 45 Evennia Documentation, Release 0.9

live instance of your site until reloaded.

If you are not running in daemon mode or want to force the issue, simply restart or reload apache2 to apply your changes.

Further notes and hints:

If you get strange (and usually uninformative) Permission denied errors from Apache, make sure that your evennia directory is located in a place the webserver may actually access. For example, some Linux distributions may default to very restrictive access permissions on a user’s /home directory.

One user commented that they had to add the following to their Apache config to get things to work. Not confirmed, but worth trying if there are trouble.

/evennia/game/web"> Options+ExecCGI Allow from all

7.4.2 mod_proxy and mod_ssl setup

Below are steps on running Evennia using a front-end proxy (Apache HTTP), mod_proxy_http, mod_proxy_wstunnel, and mod_ssl. mod_proxy_http and mod_proxy_wstunnel will simply be referred to as mod_proxy below.

Install mod_ssl

Fedora/RHEL

Apache HTTP Server and mod_ssl are available in the standard package repositories for Fedora and RHEL:

# dnf install httpd mod_ssl or # yum install httpd mod_ssl

Ubuntu/Debian

Apache HTTP Server and mod_ssljkl are installed together in the apache2 package and available in the standard package repositories for Ubuntu and Debian. mod_ssl needs to be enabled after installation:

46 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

# apt-get update # apt-get install apache2 # a2enmod ssl

TLS proxy+websocket configuration

Below is a sample configuration for Evennia with a TLS-enabled http and websocket proxy.

Apache HTTP Server Configuration

# Always redirect to https/443 ServerName mud.example.com Redirect / https://mud.example.com

ServerName mud.example.com

SSLEngine On

# Location of certificate and key SSLCertificateFile /etc/pki/tls/certs/mud.example.com.crt SSLCertificateKeyFile /etc/pki/tls/private/mud.example.com.key

# Use a tool https://www.ssllabs.com/ssltest/ to scan your set after setting up. SSLProtocol TLSv1.2 SSLCipherSuite HIGH:!eNULL:!NULL:!aNULL

# Proxy all websocket traffic to port 4002 in Evennia ProxyPass /ws ws://127.0.0.1:4002/ ProxyPassReverse /ws ws://127.0.0.1:4002/

# Proxy all HTTP traffic to port 4001 in Evennia ProxyPass / http://127.0.0.1:4001/ ProxyPassReverse / http://127.0.0.1:4001/

# Configure separate logging for this Evennia proxy ErrorLog logs/evennia_error.log CustomLog logs/evennia_access.log combined

Evennia secure websocket configuration

There is a slight trick in setting up Evennia so websocket traffic is handled correctly by the proxy. You must set the WEBSOCKET_CLIENT_URL setting in your mymud/server/conf/settings.py file:

WEBSOCKET_CLIENT_URL="wss://external.example.com/ws"

7.4. Apache Config 47 Evennia Documentation, Release 0.9

The setting above is what the client’s browser will actually use. Note the use of wss:// is because our client will be communicating over an encrypted connection (“wss” indicates websocket over SSL/TLS). Also, especially note the additional path /ws at the end of the URL. This is how Apache HTTP Server identifies that a particular request should be proxied to Evennia’s websocket port but this should be applicable also to other types of proxies (like nginx). site: http://evennia.com

7.5 Updating Your Game

Fortunately, it’s extremely easy to keep your Evennia server up-to-date. If you haven’t already, see the Getting Started guide and get everything running.

7.5.1 Updating with the latest Evennia code changes

Very commonly we make changes to the Evennia code to improve things. There are many ways to get told when to update: You can subscribe to the RSS feed or manually check up on the feeds from http://www.evennia.com. You can also simply fetch the latest regularly. When you’re wanting to apply updates, simply cd to your cloned evennia root directory and type: git pull assuming you’ve got the command line client. If you’re using a graphical client, you will probably want to navigate to the evennia directory and either right click and find your client’s pull function, or use one of the menus (if applicable). You can review the latest changes with git log or the equivalent in the graphical client. You can also see the latest changes online here. You will always need to do evennia reload (or reload from -in-game) from your game-dir to have the new code affect your game. If you want to be really sure you should run a full evennia reboot so that both Server and Portal can restart (this will disconnect everyone though, so if you know the Portal has had no updates you don’t have to do that).

7.5.2 Upgrading Evennia dependencies

On occasion we update the versions of third-party libraries Evennia depend on (or we may add a new dependency). This will be announced on the mailing list/forum. If you run into errors when starting Evennia, always make sure you have the latest versions of everything. In some cases, like for Django, starting the server may also give warning saying that you are using a working, but too-old version that should not be used in production. Upgrading evennia will automatically fetch all the latest packages that it now need. First cd to your cloned evennia folder. Make sure your virtualenv is active and use pip install--upgrade-e.

Remember the period (.) at the end - that applies the upgrade to the current location (your evennia dir).

48 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

The -e means that we are linking the evennia sources rather than copying them into the environment. This means we can most of the time just update the sources (with git pull) and see those changes directly applied to our installed evennia package. Without installing/upgrading the package with -e, we would have to remember to upgrade the package every time we downloaded any new source-code changes. Follow the upgrade output to make sure it finishes without errors. To check what packages are currently available in your python environment after the upgrade, use

pip list

This will show you the version of all installed packages. The evennia package will also show the location of its source code.

Migrating the Database Schema

Whenever we change the database layout of Evennia upstream (such as when we add new features) you will need to migrate your existing database. When this happens it will be clearly noted in the git log (it will say something to the effect of “Run migrations”). Database changes will also be announced on the Evennia mailing list. When the database schema changes, you just go to your game folder and run

evennia migrate

Hint: If the ``evennia`` command is not found, you most likely need to activate your `virtualenv`_.

Resetting your database

Should you ever want to start over completely from scratch, there is no need to re-download Evennia or anything like that. You just need to clear your database. Once you are done, you just rebuild it from scratch as described in step 2 of the Getting Started guide. First stop a running server with

evennia stop

If you run the default SQlite3 database (to change this you need to edit your settings.py file), the database is actually just a normal file in mygame/server/ called evennia.db3. Simply delete that file - that’s it. Now run evennia migrate to recreate a new, fresh one. If you run some other database system you can instead flush the database:

evennia flush

This will empty the database. However, it will not reset the internal counters of the database, so you will start with higher dbref values. If this is okay, this is all you need. Django also offers an easy way to start the database’s own management should we want more direct control:

evennia dbshell

In e.g. MySQL you can then do something like this (assuming your MySQL database is named “Evennia”:

mysql> DROP DATABASE Evennia; mysql> exit

(continues on next page)

7.5. Updating Your Game 49 Evennia Documentation, Release 0.9

(continued from previous page) NOTE: Under Windows OS, in order to access SQLite dbshell you need to `download the SQLite command-line shell program`_. It’s a single executable file (sqlite3.exe) that you should place in the root of either your MUD folder or Evennia’s (it’s the same, in both cases Django will find it).

More about schema migrations

If and when an Evennia update modifies the database schema (that is, the under-the-hood details as to how data is stored in the database), you must update your existing database correspondingly to match the change. If you don’t, the updated Evennia will complain that it cannot read the database properly. Whereas schema changes should become more and more rare as Evennia matures, it may still happen from time to time. One way one could handle this is to apply the changes manually to your database using the database’s command line. This often means adding/removing new tables or fields as well as possibly convert existing data to match what the new Evennia version expects. It should be quite obvious that this quickly becomes cumbersome and error-prone. If your database doesn’t contain anything critical yet it’s probably easiest to simply reset it and start over rather than to bother converting.

Enter migrations. Migrations keeps track of changes in the database schema and applies them automatically for you. Basically, whenever the schema changes we distribute small files called “migrations” with the source. Those tell the system exactly how to implement the change so you don’t have to do so manually. When a migration has been added we will tell you so on Evennia’s mailing lists and in commit messages - you then just run evennia migrate to be up-to-date again.

7.6 Server Conf

Evennia runs out of the box without any changes to its settings. But there are several important ways to customize the server and expand it with your own plugins.

7.6.1 Settings file

The “Settings” file referenced throughout the documentation is the file mygame/server/conf/settings.py. This is automatically created on the first run of evennia --init (see the Getting Started page). Your new settings.py is relatively bare out of the box. Evennia’s core settings file is actually even- nia/settings_default.py and is considerably more extensive (it is also heavily documented so you should refer to this file directly for the available settings). Since mygame/server/conf/settings.py is a normal Python module, it simply imports evennia/ settings_default.py into itself at the top. This means that if any setting you want to change were to depend on some other default setting, you might need to copy & paste both in order to change them and get the effect you want (for most commonly changed settings, this is not something you need to worry about). You should never edit evennia/settings_default.py. Rather you should copy&paste the select variables you want to change into your settings.py and edit them there. This will overload the previously imported defaults. Warning: It may be tempting to copy everything from settings_default.py into your own settings file. There is a reason we don’t do this out of the box though: it makes it directly clear what changes

50 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

you did. Also, if you limit your copying to the things you really need you will directly be able to take advantage of upstream changes and additions to Evennia for anything you didn’t customize. In code, the settings is accessed through from django.conf import settings # or (shorter): from evennia import settings # example: servername= settings.SERVER_NAME

Each setting appears as a property on the imported settings object. You can also explore all possible options with evennia.settings_full (this also includes advanced Django defaults that are not touched in default Evennia). It should be pointed out that when importing settings into your code like this, it will be read only. You cannot edit your settings from your code! The only way to change an Evennia setting is to edit mygame/ server/conf/settings.py directly. You also generally need to restart the server (possibly also the Portal) before a changed setting becomes available.

7.6.2 Other files in the server/conf directory

Apart from the main settings.py file, • at_initial_setup.py - this allows you to add a custom startup method to be called (only) the very first time Evennia starts (at the same time as user #1 and Limbo is created). It can be made to start your own global scripts or set up other system/world-related things your game needs to have running from the start. • at_server_startstop.py - this module contains two functions that Evennia will call every time the Server starts and stops respectively - this includes stopping due to reloading and resetting as well as shutting down completely. It’s a useful place to put custom startup code for handlers and other things that must run in your game but which has no database persistence. • connection_screens.py - all global string variables in this module are interpreted by Evennia as a greet- ing screen to show when an Account first connects. If more than one string variable is present in the module a random one will be picked. • inlinefuncs.py - this is where you can define custom Inline functions. • inputfuncs.py - this is where you define custom Input functions to handle data from the client. • lockfuncs.py - this is one of many possible modules to hold your own “safe” lock functions to make avail- able to Evennia’s Locks. • mssp.py - this holds meta information about your game. It is used by MUD search engines (which you often have to register with) in order to display what kind of game you are running along with statistics such as number of online accounts and online status. • oobfuncs.py - in here you can define custom OOB functions. • portal_services_plugin.py - this allows for adding your own custom services/protocols to the Portal. It must define one particular function that will be called by Evennia at startup. There can be any number of service plugin modules, all will be imported and used if defined. More info can be found here. • server_services_plugin.py - this is equivalent to the previous one, but used for adding new services to the Server instead. More info can be found here. Some other Evennia systems can be customized by plugin modules but has no explicit template in conf/: • cmdparser.py - a custom module can be used to totally replace Evennia’s default command parser. All this does is to split the incoming string into “command name” and “the rest”. It also handles things like error messages for no-matches and multiple-matches among other things that makes this more complex than it sounds. The

7.6. Server Conf 51 Evennia Documentation, Release 0.9

default parser is very generic, so you are most often best served by modifying things further down the line (on the command parse level) than here. • at_search.py - this allows for replacing the way Evennia handles search results. It allows to change how errors are echoed and how multi-matches are resolved and reported (like how the default understands that “2-ball” should match the second “ball” object if there are two of them in the room).

7.6.3 ServerConf

There is a special database model called ServerConf that stores server internal data and settings such as current account count (for interfacing with the webserver), startup status and many other things. It’s rarely of use outside the server core itself but may be good to know about if you are an Evennia developer.

7.7 Online Setup

Evennia development can be made without any Internet connection beyond fetching updates. At some point however, you are likely to want to make your game visible online, either as part opening it to the public or to allow other developers or beta testers access to it.

7.7.1 Connecting from the outside

Accessing your Evennia server from the outside is not hard on its own. Any issues are usually due to the various security measures your computer, network or hosting service has. These will generally (and correctly) block outside access to servers on your machine unless you tell them otherwise. We will start by showing how to host your server on your own local computer. Even if you plan to host your “real” game on a remote host later, setting it up locally is useful practice. We cover remote hosting later in this document. Out of the box, Evennia uses three ports for outward communication. If your computer has a firewall, these should be open for in/out communication (and only these, other ports used by Evennia are internal to your computer only). • 4000, telnet, for traditional mud clients • 4001, HTTP for the website) • 4002, websocket, for the web client Evennia will by default accept incoming connections on all interfaces (0.0.0.0) so in principle anyone knowing the ports to use and has the IP address to your machine should be able to connect to your game. • Make sure Evennia is installed and that you have activated the virtualenv. Start the server with evennia start --log. The --log (or -l) will make sure that the logs are echoed to the terminal. Note: If you need to close the log-view, use Ctrl-C. Use just evennia --log on its own to start tailing the logs again. • Make sure you can connect with your web browser to http://localhost:4001 or, alternatively, http:127.0.0.1:4001 which is the same thing. You should get your Evennia web site and be able to play the game in the web client. Also check so that you can connect with a to host localhost, port 4000 or host 127.0.0.1, port 4000. • Google for “my ip” or use any online service to figure out what your “outward-facing” IP address is. For our purposes, let’s say your outward-facing IP is 203.0.113.0.

52 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

• Next try your outward-facing IP by opening http://203.0.113.0:4001 in a browser. If this works, that’s it! Also try telnet, with the server set to 203.0.113.0 and port 4000. However, most likely it will not work. If so, read on. • If your computer has a firewall, it may be blocking the ports we need (it may also block telnet overall). If so, you need to open the outward-facing ports to in/out communication. See the manual/instructions for your firewall software on how to do this. To test you could also temporarily turn off your firewall entirely to see if that was indeed the problem. • Another common problem for not being able to connect is that you are using a hardware router (like a wifi router). The router sits ‘between’ your computer and the Internet. So the IP you find with Google is the router’s IP, not that of your computer. To resolve this you need to configure your router to forward data it gets on its ports to the IP and ports of your computer sitting in your private network. How to do this depends on the make of your router; you usually configure it using a normal web browser. In the router interface, look for “Port forwarding” or maybe “Virtual server”. If that doesn’t work, try to temporarily wire your computer directly to the Internet outlet (assuming your computer has the ports for it). You’ll need to check for your IP again. If that works, you know the problem is the router. Note: If you need to reconfigure a router, the router’s Internet-facing ports do not have to have to have the same numbers as your computer’s (and Evennia’s) ports! For example, you might want to connect Evennia’s outgoing port 4001 to an outgoing router port 80 - this is the port HTTP requests use and web browsers automatically look for - if you do that you could go to http://203.0. 113.0 without having to add the port at the end. This would collide with any other web services you are running through this router though.

Settings example

You can connect Evennia to the Internet without any changes to your settings. The default settings are easy to use but are not necessarily the safest. You can customize your online presence in your settings file. To have Evennia recognize changed port settings you have to do a full evennia reboot to also restart the Portal and not just the Server component. Below is an example of a simple set of settings, mostly using the defaults. Evennia will require access to five computer ports, of which three (only) should be open to the outside world. Below we continue to assume that our server address is 203.0.113.0.

# in mygame/server/conf/settings.py

SERVERNAME="MyGame"

# open to the internet: 4000, 4001, 4002 # closed to the internet (internal use): 4005, 4006 TELNET_PORTS=[4000] WEBSOCKET_CLIENT_PORT= 4002 WEBSERVER_PORTS=[(4001, 4005)] AMP_PORT= 4006

# Optional - security measures limiting interface access # (don't set these before you know things work without them) TELNET_INTERFACES=['203.0.113.0'] WEBSOCKET_CLIENT_INTERFACE='203.0.113.0' ALLOWED_HOSTS=[".mymudgame.com"]

# uncomment if you want to lock the server down for maintenance. # LOCKDOWN_MODE = True

Read on for a description of the individual settings.

7.7. Online Setup 53 Evennia Documentation, Release 0.9

Telnet

# Required. Change to whichever outgoing Telnet port(s) # you are allowed to use on your host. TELNET_PORTS=[4000] # Optional for security. Restrict which telnet # interfaces we should accept. Should be set to your # outward-facing IP address(). Default is ´0.0.0.0´ # which accepts all interfaces. TELNET_INTERFACES=['0.0.0.0']

The TELNET_* settings are the most important ones for getting a traditional base game going. Which IP addresses you have available depends on your server hosting solution (see the next sections). Some hosts will restrict which ports you are allowed you use so make sure to check.

Web server

# Required. This is a list of tuples # (outgoing_port, internal_port). Only the outgoing # port should be open to the world! # set outgoing port to 80 if you want to run Evennia # as the only web server on your machine (if available). WEBSERVER_PORTS=[(4001, 4005)] # Optional for security. Change this to the IP your # server can be reached at (normally the same # as TELNET_INTERFACES) WEBSERVER_INTERFACES=['0.0.0.0'] # Optional for security. Protects against # man-in-the-middle attacks. Change it to your server's # IP address or URL when you run a production server. ALLOWED_HOSTS=[' *']

The web server is always configured with two ports at a time. The outgoing port (4001 by default) is the port external connections can use. If you don’t want users to have to specify the port when they connect, you should set this to 80 - this however only works if you are not running any other web server on the machine. The internal port (4005 by default) is used internally by Evennia to communicate between the Server and the Portal. It should not be available to the outside world. You usually only need to change the outgoing port unless the default internal port is clashing with some other program.

Web client

# Required. Change this to the main IP address of your server. WEBSOCKET_CLIENT_INTERFACE='0.0.0.0' # Optional and needed only if using a proxy or similar. Change # to the IP or address where the client can reach # your server. The ws:// part is then required. If not given, the client # will use its host location. WEBSOCKET_CLIENT_URL="" # Required. Change to a free port for the websocket client to reach # the server on. This will be automatically appended # to WEBSOCKET_CLIENT_URL by the web client. WEBSOCKET_CLIENT_PORT= 4002

54 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

The websocket-based web client needs to be able to call back to the server, and these settings must be changed for it to find where to look. If it cannot find the server you will get an warning in your browser’s Console (in the dev tools of the browser), and the client will revert to the AJAX-based of the client instead, which tends to be slower.

Other ports

# Optional public facing. Only allows SSL connections (off by default). SSL_PORTS=[4003] SSL_INTERFACES=['0.0.0.0'] # Optional public facing. Only if you allow SSH connections (off by default). SSH_PORTS=[4004] SSH_INTERFACES=['0.0.0.0'] # Required private. You should only change this if there is a clash # with other services on your host. Should NOT be open to the # outside world. AMP_PORT= 4006

The AMP_PORT is required to work, since this is the internal port linking Evennia’s Server and Portal components together. The other ports are encrypted ports that may be useful for custom protocols but are otherwise not used.

Lockdown mode

When you test things out and check configurations you may not want players to drop in on you. Similarly, if you are doing maintenance on a live game you may want to take it offline for a while to fix eventual problems without risking people connecting. To do this, stop the server with evennia stop and add LOCKDOWN_MODE = True to your settings file. When you start the server again, your game will only be accessible from localhost.

Registering with the Evennia game directory

Once your game is online you should make sure to register it with the Evennia Game Index. Registering with the index will help people find your server, drum up interest for your game and also shows people that Evennia is being used. You can do this even if you are just starting development - if you don’t give any telnet/web address it will appear as Not yet public and just be a teaser. If so, pick pre-alpha as the development status. To register, stand in your game dir, run

evennia connections

and follow the instructions. See the Game index page for more details.

7.7.2 SSL

SSL can be very useful for web clients. It will protect the credentials and gameplay of your users over a web client if they are in a public place, and your websocket can also be switched to WSS for the same benefit. SSL certificates used to cost money on a yearly basis, but there is now a program that issues them for free with assisted setup to make the entire process less painful. Options that may be useful in combination with an SSL proxy:

# See above for the section on Lockdown Mode. # Useful for a proxy on the public interface connecting to Evennia on localhost. LOCKDOWN_MODE= True (continues on next page)

7.7. Online Setup 55 Evennia Documentation, Release 0.9

(continued from previous page)

# Have clients communicate via wss after connecting with https to port 4001. # Without this, you may get DOMException errors when the browser tries # to create an insecure websocket from a secure webpage. WEBSOCKET_CLIENT_URL="wss://fqdn:4002"

Let’s Encrypt

Let’s Encrypt is a certificate authority offering free certificates to secure a website with HTTPS. To get started issuing a certificate for your web server using Let’s Encrypt, see these links: • Let’s Encrypt - Getting Started • The CertBot Client is a program for automatically obtaining a certificate, use it and maintain it with your website. Also, on Freenode visit the #letsencrypt channel for assistance from the community. For an additional resource, Let’s Encrypt has a very active community forum. A blog where someone sets up Let’s Encrypt The only process missing from all of the above documentation is how to pass verification. This is how Let’s Encrypt verifies that you have control over your domain (not necessarily ownership, it’s Domain Validation (DV)). This can be done either with configuring a certain path on your web server or through a TXT record in your DNS. Which one you will want to do is a personal preference, but can also be based on your hosting choice. In a controlled/cPanel environment, you will most likely have to use DNS verification.

7.7.3 Relevant SSL Proxy Setup Information

• Apache webserver configuration (optional) • HAProxy Config

7.7.4 Hosting locally or remotely?

Using your own computer as a server

What we showed above is by far the simplest and probably cheapest option: Run Evennia on your own home computer. Moreover, since Evennia is its own web server, you don’t need to install anything extra to have a website. Advantages • Free (except for internet costs and the electrical bill). • Full control over the server and hardware (it sits right there!). • Easy to set up. • Suitable for quick setups - e.g. to briefly show off results to your collaborators. Disadvantages • You need a good internet connection, ideally without any upload/download limits/costs. • If you want to run a full game this way, your computer needs to always be on. It could be noisy, and as mentioned, the electrical bill must be considered. • No support or safety - if your house burns down, so will your game. Also, you are yourself responsible for doing regular backups.

56 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

• Potentially not as easy if you don’t know how to open ports in your firewall or router. • Home IP numbers are often dynamically allocated, so for permanent online time you need to set up a DNS to always re-point to the right place (see below).

Setting up your own machine as a server

The first section of this page describes how to do this and allow users to connect to the IP address of your ma- chine/router. A complication with using a specific IP address like this is that your home IP might not remain the same. Many ISPs (Internet Service Providers) allocates a dynamic IP to you which could change at any time. When that happens, that IP you told people to go to will be worthless. Also, that long string of numbers is not very pretty, is it? It’s hard to remember and not easy to use in marketing your game. What you need is to alias it to a more sensible domain name - an alias that follows you around also when the IP changes. 1. To set up a domain name alias, we recommend starting with a free domain name from FreeDNS. Once you register there (it’s free) you have access to tens of thousands domain names that people have “donated” to allow you to use for your own sub domain. For example, strangled.net is one of those available domains. So tying our IP address to strangled.net using the subdomain evennia would mean that one could henceforth direct people to http://evennia.strangled.net:4001 for their gaming needs - far easier to remember! 2. So how do we make this new, nice domain name follow us also if our IP changes? For this we need to set up a little program on our computer. It will check whenever our ISP decides to change our IP and tell FreeDNS that. There are many alternatives to be found from FreeDNS:s homepage, one that works on multiple platforms is inadyn. Get it from their page or, in Linux, through something like apt-get install inadyn. 3. Next, you login to your account on FreeDNS and go to the Dynamic page. You should have a list of your subdomains. Click the Direct URL link and you’ll get a page with a text message. Ignore that and look at the URL of the page. It should be ending in a lot of random letters. Everything after the is your unique “hash”. Copy this string. 4. You now start inadyn with the following command (Linux): inadyn --dyndns_system [email protected] -a , & where would be evennia.strangled.net and the string of numbers we copied from FreeDNS. The & means we run in the background (might not be valid in other operating systems). inadyn will henceforth check for changes every 60 seconds. You should put the inadyn command string in a startup script somewhere so it kicks into gear whenever your computer starts.

Remote hosting

Your normal “web hotel” will probably not be enough to run Evennia. A web hotel is normally aimed at a very specific usage - delivering web pages, at the most with some dynamic content. The “Python scripts” they refer to on their home pages are usually only intended to be CGI-like scripts launched by their webserver. Even if they allow you shell access (so you can install the Evennia dependencies in the first place), resource usage will likely be very restricted. Running a full-fledged game server like Evennia will probably be shunned upon or be outright impossible. If you are unsure, contact your web hotel and ask about their policy on you running third-party servers that will want to open custom ports. The options you probably need to look for are shell account services, VPS:es or Cloud services. A “Shell account” service means that you get a shell account on a server and can log in like any normal user. By contrast, a VPS (Virtual Private Server) service usually means that you get root access, but in a virtual machine. There are also Cloud-type services which allows for starting up multiple virtual machines and pay for what resources you use.

7.7. Online Setup 57 Evennia Documentation, Release 0.9

Advantages • Shell accounts/VPS/clouds offer more flexibility than your average web hotel - it’s the ability to log onto a shared computer away from home. • Usually runs a Linux flavor, making it easy to install Evennia. • Support. You don’t need to maintain the server hardware. If your house burns down, at least your game stays online. Many services guarantee a certain level of up-time and also do regular backups for you. Make sure to check, some offer lower rates in exchange for you yourself being fully responsible for your data/backups. • Usually offers a fixed domain name, so no need to mess with IP addresses. • May have the ability to easily deploy docker versions of evennia and/or your game. Disadvantages • Might be pretty expensive (more so than a web hotel). Note that Evennia will normally need at least 100MB RAM and likely much more for a large production game. • Linux flavors might feel unfamiliar to users not used to ssh/PuTTy and the Linux command line. • You are probably sharing the server with many others, so you are not completely in charge. CPU usage might be limited. Also, if the server people decides to take the server down for maintenance, you have no choice but to sit it out (but you’ll hopefully be warned ahead of time).

Installing Evennia on a remote server

Firstly, if you are familiar with server infrastructure, consider using Docker to deploy your game to the remote server; it will likely ease installation and deployment. Docker images may be a little confusing if you are completely new to them though. If not using docker, and assuming you know how to connect to your account over ssh/PuTTy, you should be able to follow the Getting Started instructions normally. You only need Python and GIT pre-installed; these should both be available on any servers (if not you should be able to easily ask for them to be installed). On a VPS or Cloud service you can install them yourself as needed. If virtualenv is not available and you can’t get it, you can download it (it’s just a single file) from the virtualenv pypi. Using virtualenv you can install everything without actually needing to have further root access. Ports might be an issue, so make sure you know which ports are available to use and reconfigure Evennia accordingly.

Hosting options

To find commercial solutions, browse the web for “shell access”, “VPS” or “Cloud services” in your region. You may find useful offers for “low cost” VPS hosting on Low End Box. The associated Low End Talk forum can be useful for health checking the many small businesses that offer “value” hosting, and occasionally for technical suggestions. There are all sorts of services available. Below are some international suggestions offered by Evennia users:

58 Chapter 7. Installation and setup Evennia Documentation, Release 0.9

Host- Type Low- Comments ing est name price sil- Shell Free Private hobby provider so don’t assume backups or expect immediate support. To ask vren.comac- for for an account, connect with a MUD client to iweb.localecho.ne t, port 4201 and ask for count MU* “Jarin”. Dig- VPS $5/monthYou can get a $50 credit if you use the referral link https://m.do.co/c /8f64fec2670c - if ital you do, once you’ve had it long enough to have paid $25 we will get that as a referral Ocean bonus to help Evennia development. Ama- Cloud ~$5/monthFree Tier first 12 months. Regions available around the globe. zon / on- Web demand ser- vices Ama- Cloud $5/monthFree first month. AWS’s new “fixed cost” offering. zon Light- sail Gen- Shell $8/monthDedicated MUD host with very limited memory offerings. As for 2017, runs a 13 years old esis ac- Python version (2.4) so you’d need to either convince them to update or compile yourself. MUD count Note that Evennia needs at least the “Deluxe” package (50MB RAM) and probably a lot host- higher for a production game. This host is not recommended for Evennia. ing Host1PlusVPS $4/month$4-$8/month depending on length of sign-up period. & Cloud Scale- Cloud C3/monthEU based (Paris, Amsterdam). Smallest option provides 2GB RAM. way / on- demand Prgmr VPS $5/month1 month free with a year prepay. You likely want some experience with servers with this option as they don’t have a lot of support. Lin- Cloud $5/monthMultiple regions. Smallest option provides 1GB RAM ode / on- demand

Please help us expand this list.

7.7.5 Cloud9

If you are interested in running Evennia in the online dev environment Cloud9, you can spin it up through their normal online setup using the Evennia Linux install instructions. The one extra thing you will have to do is update mygame/ server/conf/settings.py and add WEBSERVER_PORTS = [(8080, 4001)]. This will then let you access the web server and do everything else as normal. Note that, as of December 2017, Cloud9 was re-released by Amazon as a service within their AWS cloud service offering. New customers entitled to the 1 year AWS “free tier” may find it provides sufficient resources to operate a Cloud9 development environment without charge. https://aws.amazon.com/cloud9/

7.7. Online Setup 59 Evennia Documentation, Release 0.9

60 Chapter 7. Installation and setup CHAPTER 8

Admin Documentation

This chapter is aimed at game administrators – the higher-ups that possess shell access and are responsible for manag- ing the game.

8.1 Banning

Whether due to abuse, blatant breaking of your rules, or some other reason, you will eventually find no other recourse but to kick out a particularly troublesome player. The default command set has admin tools to handle this, primarily @ban, @unban, and @boot.

8.1.1 Creating a ban

Say we have a troublesome player “YouSuck” - this is a person that refuses common courtesy - an abusive and spammy account that is clearly created by some bored internet hooligan only to cause grief. You have tried to be nice. Now you just want this troll gone.

Name ban

The easiest recourse is to block the account YouSuck from ever connecting again.

@ban YouSuck

This will lock the name YouSuck (as well as ‘yousuck’ and any other capitalization combination), and next time they try to log in with this name the server will not let them! You can also give a reason so you remember later why this was a good thing (the banned account will never see this)

@ban YouSuck:This is just a troll.

If you are sure this is just a account, you might even consider deleting the player account outright:

61 Evennia Documentation, Release 0.9

@delaccount YouSuck

Generally, banning the name is the easier and safer way to stop the use of an account – if you change your mind you can always remove the block later whereas a deletion is permanent.

IP ban

Just because you block YouSuck’s name might not mean the trolling human behind that account gives up. They can just create a new account YouSuckMore and be back at it. One way to make things harder for them is to tell the server to not allow connections from their particular IP address. First, when the offending account is online, check which IP address they use. This you can do with the who command, which will show you something like this:

Account Name On for Idle Room Cmds Host YouSuckMore 01:122m 22 212 237.333.0.223

The “Host” bit is the IP address from which the account is connecting. Use this to define the ban instead of the name:

@ban 237.333.0.223

This will stop YouSuckMore connecting from their computer. Note however that IP address might change easily - either due to how the player’s Internet Service Provider operates or by the user simply changing computers. You can make a more general ban by putting * as wildcards for the groups of three digits in the address. So if you figure out that !YouSuckMore mainly connects from 237.333.0.223, 237.333.0.225, and 237.333.0.256 (only changes in their subnet), it might be an idea to put down a ban like this to include any number in that subnet:

@ban 237.333.0.*

You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly locked regardless of where they connect from. Be careful with too general IP bans however (more asterisks above). If you are unlucky you could be blocking out innocent players who just happen to connect from the same subnet as the offender.

8.1.2 Booting

YouSuck is not really noticing all this banning yet though - and won’t until having logged out and trying to log back in again. Let’s help the troll along.

@boot YouSuck

Good riddance. You can give a reason for booting too (to be echoed to the player before getting kicked out).

@boot YouSuck:Go troll somewhere else.

Lifting a ban

Use the @unban (or @ban) command without any arguments and you will see a list of all currently active bans:

Active bans id name/ip date reason 1 yousuck Fri Jan3 23:00:22 2020 This is just a Troll. 2 237.333.0.* Fri Jan3 23:01:03 2020 YouSuck's IP.

62 Chapter 8. Admin Documentation Evennia Documentation, Release 0.9

Use the id from this list to find out which ban to lift.

@unban 2

Cleared ban2: 237.333.0. *

8.1.3 Summary of abuse-handling tools

Below are other useful commands for dealing with annoying players. • who – (as admin) Find the IP of a account. Note that one account can be connected to from multiple IPs depending on what you allow in your settings. • examine/account thomas – Get all details about an account. You can also use *thomas to get the account. If not given, you will get the Object thomas if it exists in the same location, which is not what you want in this case. • boot thomas – Boot all sessions of the given account name. • boot 23 – Boot one specific client session/IP by its unique id. • ban – List all bans (listed with ids) • ban thomas – Ban the user with the given account name • ban/ip ‘‘134.233.2.111‘‘ – Ban by IP • ban/ip ‘‘134.233.2.*‘‘ – Widen IP ban • ban/ip ‘‘134.233.*.*‘‘ – Even wider IP ban • unban 34 – Remove ban with id #34 • cboot mychannel = thomas – Boot a subscriber from a channel you control • clock mychannel = control:perm(Admin);listen:all();send:all() – Fine control of access to your channel using lock definitions. Locking a specific command (like page) is accomplished like so: 1. Examine the source of the command. The default ‘‘page‘ command class‘_ has the lock string “cmd:not pperm(page_banned)”. This means that unless the player has the ‘permission’ “page_banned” they can use this command. You can assign any lock string to allow finer customization in your commands. You might look for the value of an Attribute or Tag, your current location etc. 2. perm/account thomas = page_banned – Give the account the ‘permission’ which causes (in this case) the lock to fail. • perm/del/account thomas = page_banned – Remove the given permission • tel thomas = jail – Teleport a player to a specified location or #dbref • type thomas = FlowerPot – Turn an annoying player into a flower pot (assuming you have a FlowerPot typeclass ready) • userpassword thomas = fooBarFoo – Change a user’s password • delaccount thomas – Delete a player account (not recommended, use ban instead) • server – Show server statistics, such as CPU load, memory usage, and how many objects are cached • time – Gives server uptime, runtime, etc • reload – Reloads the server without disconnecting anyone

8.1. Banning 63 Evennia Documentation, Release 0.9

• reset – Restarts the server, kicking all connections • shutdown – Stops the server cold without it auto-starting again • py – Executes raw Python code, allows for direct inspection of the database and account objects on the fly. For advanced users.

8.2 IRC

IRC () is a long standing chat protocol used by many open-source projects for communicating in real time. By connecting one of Evennia’s Channels to an IRC channel you can communicate also with people not on an mud themselves. You can also use IRC if you are only running your Evennia MUD locally on your computer (your game doesn’t need to be open to the public)! All you need is an internet connection. For IRC operation you also need twisted.words. This is available simply as a package python-twisted-words in many Linux distros, or directly downloadable from the link.

8.2.1 Configuring IRC

To configure IRC, you’ll need to activate it in your settings file.

IRC_ENABLED= True

Start Evennia and log in as a privileged user. You should now have a new command available: @irc2chan. This command is called like this:

@irc2chan[/switches]=< #irchannel>

If you already know how IRC works, this should be pretty self-evident to use. Read the help entry for more features.

8.2.2 Setting up IRC, step by step

You can connect IRC to any Evennia channel (so you could connect it to the default public channel if you like), but for testing, let’s set up a new channel irc.

@ccreate irc = This is connected to an irc channel!

You will automatically join the new channel. Next we will create a connection to an external IRC network and channel. There are many, many IRC nets. Here is a list of some of the biggest ones, the one you choose is not really very important unless you want to connect to a particular channel (also make sure that the network allows for “bots” to connect). For testing, we choose the Freenode network, irc.freenode.net. We will connect to a test channel, let’s call it #myevennia-test (an IRC channel always begins with #). It’s best if you pick an obscure channel name that didn’t exist previously - if it didn’t exist it will be created for you. Don’t connect to #evennia for testing and debugging, that is Evennia’s official chat channel! You are welcome to connect your game to #evennia once you have everything working though - it can be a good way to get help and ideas. But if you do, please do so with an in-game channel open only to your game admins and developers). The port needed depends on the network. For Freenode this is 6667. What will happen is that your Evennia server will connect to this IRC channel as a normal user. This “user” (or “bot”) needs a name, which you must also supply. Let’s call it “mud-bot”.

64 Chapter 8. Admin Documentation Evennia Documentation, Release 0.9

To test that the bot connects correctly you also want to log onto this channel with a separate, third-party IRC client. There are hundreds of such clients available. If you use Firefox, the Chatzilla plugin is good and easy. Freenode also offers its own web-based chat page. Once you have connected to a network, the command to join is usually /join #channelname (don’t forget the #). Next we connect Evennia with the IRC channel.

@irc2chan irc= irc.freenode.net 6667 #myevennia-test mud-bot

Evennia will now create a new IRC bot mud-bot and connect it to the IRC network and the channel #myevennia. If you are connected to the IRC channel you will soon see the user mud-bot connect. Write something in the Evennia channel irc.

irc Hello, World! [irc] Anna: Hello, World!

If you are viewing your IRC channel with a separate IRC client you should see your text appearing there, spoken by the bot: mud-bot> [irc] Anna: Hello, World!

Write Hello! in your IRC client window and it will appear in your normal channel, marked with the name of the IRC channel you used (#evennia here).

[irc] Anna@#myevennia-test: Hello!

Your Evennia gamers can now chat with users on external IRC channels!

8.3 RSS

RSS is a format for easily tracking updates on . The principle is simple - whenever a site is updated, a small text file is updated. An RSS reader can then regularly go online, check this file for updates and let the user know what’s new. Evennia allows for connecting any number of RSS feeds to any number of in-game channels. Updates to the feed will be conveniently echoed to the channel. There are many potential uses for this: For example the MUD might use a separate website to host its forums. Through RSS, the players can then be notified when new posts are made. Another example is to let everyone know you updated your dev blog. Admins might also want to track the latest Evennia updates through our own RSS feed here.

8.3.1 Configuring RSS

To use RSS, you first need to install the feedparser python module. pip install feedparser

Next you activate RSS support in your config file by settting RSS_ENABLED=True. Start/reload Evennia as a privileged user. You should now have a new command available, @rss2chan:

@rss2chan =

8.3. RSS 65 Evennia Documentation, Release 0.9

Setting up RSS, step by step

You can connect RSS to any Evennia channel, but for testing, let’s set up a new channel “rss”.

@ccreate rss = RSS feeds are echoed to this channel!

Let’s connect Evennia’s code-update feed to this channel. The RSS url for evennia updates is https://github. com/evennia/evennia/commits/master.atom, so let’s add that:

@rss2chan rss= https://github.com/evennia/evennia/commits/master.atom

That’s it, really. New Evennia updates will now show up as a one-line title and link in the channel. Give the @rss2chan command on its own to show all connections. To remove a feed from a channel, you specify the connection again (use the command to see it in the list) but add the /delete switch:

@rss2chan/delete rss= https://github.com/evennia/evennia/commits/master.atom

You can connect any number of RSS feeds to a channel this way. You could also connect them to the same channels as IRC to have the feed echo to external chat channels as well.

8.4 Text Encodings

Evennia is a text-based game server. This makes it important to understand how it actually deals with data in the form of text.

Text byte encodings describe how a string of text is actually stored in the computer - that is, the particular sequence of bytes used to represent the letters of your particular alphabet. A common encoding used in English-speaking languages is the ASCII encoding. This describes the letters in the English alphabet (Aa-Zz) as well as a bunch of special characters. For describing other character sets (such as that of other languages with other letters than English), sets with names such as Latin-1, ISO-8859-3 and ARMSCII-8 are used. There are hundreds of different byte encodings in use around the world.

A string of letters in a byte encoding is represented with the bytes type. In contrast to the byte encoding is the representation. In Python this is the str type. The unicode is an internationally agreed-upon table describing essentially all available letters you could ever want to print. Everything from English to Chinese alphabets and all in between. So what Evennia (as well as Python and Django) does is to store everything in Unicode internally, but then converts the data to one of the encodings whenever outputting data to the user.

An easy memory aid is that bytes are what are sent over the network wire. At all other times, str (unicode) is used. This means that we must convert between the two at the points where we send/receive network data.

66 Chapter 8. Admin Documentation Evennia Documentation, Release 0.9

The problem is that when receiving a string of bytes over the network it’s impossible for Evennia to guess which encoding was used - it’s just a bunch of bytes! Evennia must know the encoding in order to convert back and from the correct unicode representation.

8.4.1 How to customize encodings

As long as you stick to the standard ASCII character set (which means the normal English characters, basically) you should not have to worry much about this section.

If you want to build your game in another language however, or expect your users to want to use special characters not in ASCII, you need to consider which encodings you want to support.

As mentioned, there are many, many byte-encodings used around the world. It should be clear at this point that Evennia can’t guess but has to assume or somehow be told which encoding you want to use to communicate with the server. Basically the encoding used by your client must be the same encoding used by the server. This can be customized in two complementary ways.

1. Point users to the default @encoding command or the @options command. This allows them to themselves set which encoding they (and their client of choice) uses. Whereas data will remain stored as unicode strings internally in Evennia, all data received from and sent to this particular player will be converted to the given format before transmitting. 2. As a back-up, in case the user-set encoding translation is erroneous or fails in some other way, Evennia will fall back to trying with the names defined in the settings variable ENCODINGS. This is a list of encoding names Evennia will try, in order, before giving up and giving an encoding error message.

Note that having to try several different encodings every input/output adds unneccesary overhead. Try to guess the most common encodings you players will use and make sure these are tried first. The International UTF-8 encoding is what Evennia assumes by default (and also what Python/Django use normally). See the Wikipedia article here for more help.

8.5 Internationalization

Internationalization (often abbreviated i18n since there are 18 characters between the first “i” and the last “” in that word) allows Evennia’s core server to return texts in other languages than English - without anyone having to edit the source code. Take a look at the locale directory of the Evennia installation, there you will find which languages are currently supported.

8.5. Internationalization 67 Evennia Documentation, Release 0.9

8.5.1 Changing server language

Change language by adding the following to your mygame/server/conf/settings.py file:

USE_I18N= True LANGUAGE_CODE='en'

Here 'en' should be changed to the abbreviation for one of the supported languages found in locale/. Restart the server to activate i18n. The two-character international language codes are found here. Windows Note: If you get errors concerning gettext or xgettext on Windows, see the Django documentation. A self-installing and up-to-date version of gettext for Windows (32/64-bit) is available on Github.

8.5.2 Translating Evennia

Important Note: Evennia offers translations of hard-coded strings in the server, things like “Connection closed” or “Server restarted”, strings that end users will see and which game devs are not supposed to change on their own. Text you see in the log file or on the command line (like error messages) are generally not translated (this is a part of Python). In addition, text in default Commands and in default Typeclasses will not be translated by switching i18n language. To translate Commands and Typeclass hooks you must overload them in your game directory and translate their returns to the language you want. This is because from Evennia’s perspective, adding i18n code to commands tend to add complexity to code that is meant to be changed anyway. One of the goals of Evennia is to keep the user-changeable code as clean and easy-to-read as possible. If you cannot find your language in evennia/locale/ it’s because noone has translated it yet. Alternatively you might have the language but find the translation bad . . . You are welcome to help improve the situation! To start a new translation you need to first have cloned the Evennia repositry with GIT and activated a python virtualenv as described on the Getting Started page. You now need to cd to the evennia/ directory. This is not your created game folder but the main Evennia library folder. If you see a folder locale/ then you are in the right place. From here you run:

evennia makemessages

where is the two-letter locale code for the language you want, like ‘sv’ for Swedish or ‘es’ for Spanish. After a moment it will tell you the language has been processed. For instance:

evennia makemessages sv

If you started a new language a new folder for that language will have emerged in the locale/ folder. Otherwise the system will just have updated the existing translation with eventual new strings found in the server. Running this command will not overwrite any existing strings so you can run it as much as you want. Note: in Django, the makemessages command prefixes the locale name by the -l option (... makemessages -l sv for instance). This syntax is not allowed in Evennia, due to the fact that -l is the option to tail log files. Hence, makemessages doesn’t use the -l flag. Next head to locale//LC_MESSAGES and edit the **.po file you find there. You can edit this with a normal text editor but it is easiest if you use a special po-file editor from the web (search the web for “po editor” for many free alternatives).

The concept of translating is simple, it’s just a matter of taking the english strings you find in the **.po file and add your language’s translation best you can. The **.po format (and many

68 Chapter 8. Admin Documentation Evennia Documentation, Release 0.9

supporting editors) allow you to mark translations as “fuzzy”. This tells the system (and future translators) that you are unsure about the translation, or that you couldn’t find a translation that exactly matched the intention of the original text. Other translators will see this and might be able to improve it later. Finally, you need to compile your translation into a more efficient form. Do so from the evennia folder again:

evennia compilemessages

This will go through all languages and create/update compiled files (**.mo) for them. This needs to be done whenever a **.po file is updated. When you are done, send the **.po and *.mo file to the Evennia developer list (or push it into your own repository clone) so we can integrate your translation into Evennia!

8.6 Client Support Grid

This grid tries to gather Evennia-specific knowledge about the various clients and protocols used. Everyone’s welcome to report their findings.

8.6.1 Legend:

• Name: The name of the client. If it’s only available for a specific OS, it should be noted here too. • Version: Which version or range of client versions were tested. • Comments: Any comments or quirks on using this client with Evennia should be added here. Also note if some other protocol than Telnet is used (like Websockets, SSH etc).

8.6. Client Support Grid 69 Evennia Documentation, Release 0.9

Client Grid

Name Ver- Comments sion Evennia 0.6 Uses WS/AJAX. Current client issues web- client tintin++ 2.0+ No MXP support tinyfugue 5.0+ No UTF-8 support MUSH- 4.94 NAWS reports full text area client(Win) Zmud(Win) 7.21 UNTESTED Cmud(Win)v3 UNTESTED Potato 2.0.0b16No MXP, MCCP support. Win 32bit does not understand “localhost”, must use 127.0.0.1. Newline issue. *Won’t send a single blank line on Enter press. Mudlet 3.4+ No known issues. Some older versions showed <> as html under MXP. Sim- full UNTESTED. Discontinued. NAWS reports pixel size. pleMU(Win) At- 0.9.9.4 No known issues. lantis(Mac) GMUD 0.0.1 Can’t handle any telnet handshakes. Not recommended. BeipMU(Win)3.0.255 No MXP support. Best to enable “MUD prompt handling”, disable “Handle HTML tags”. Mu- 1.8.7 Bad Telnet Protocol compliance: displays spurious characters. dRam- mer(IOS) MUD- 1.3.1 UNTESTED Mas- ter(IOS) Blow- 1.1.3 *Telnet NOP displays as spurious character. Torch(Andr) Muk- 2015.11.20*Telnet NOP displays as spurious character. Has UTF-8/Emoji support. luk(Andr) Gnome- 0.11.2 Telnet handshake errors. First (only) attempt at logging in fails. MUD(Unix) Spyrit 0.4 No MXP, OOB support. JamochaMUD5.2 Does not support ANSI within MXP text. Duck- 4.2 No MXP support. Displays Telnet Go-Ahead and WILL SUPPRESS-GO-AH EAD as ù char- Client(Chrome) acter. Also seems to run the version command on connection, which will not work in MULTISESSION _MODES above 1. Kild- 2.11.1 No known issues. Client

Workarounds for client issues:

Issue: Telnet NOP displays as spurious character.

Known Clients

• BlowTorch(Andr) • Mukluk(Andr)

70 Chapter 8. Admin Documentation Evennia Documentation, Release 0.9

Workarounds

• Set the command in game to @option NOPKEEPALIVE=off for the session, or use the /save parameter to disable it for that Evennian account permanently. • Client-side: Set a gag-type trigger on the NOP character to make it invisible to the client.

Issue: Won’t send blank line on Enter key press.

Known Clients

• Potato

Workaround

• Press Control Enter, then Enter key again to send blank line.

8.6. Client Support Grid 71 Evennia Documentation, Release 0.9

72 Chapter 8. Admin Documentation CHAPTER 9

Builder Documentation

This chapter contains information useful to world builders and designers.

9.1 Building Quickstart

The default command definitions coming with Evennia follows a style similar to that of MUX, so the commands should be familiar if you used any such code bases before.

Throughout the larger documentation you may come across commands prefixed with @. This is just an optional marker used in some places to make a command stand out. Evennia defaults to ignoring the use of @ in front of your command (so entering dig is the same as entering @dig). The default commands have the following style (where [...] marks optional parts): command[/switch/switch...] [arguments...]

A switch is a special, optional flag to the command to make it behave differently. It is always put directly after the command name, and begins with a forward slash (/). The arguments are one or more inputs to the commands. It’s common to use an equal sign (=) when assigning something to an object. Below are some examples of commands you can try when logged in to the game. Use help for learning more about each command and their detailed options.

9.1.1 Stepping Down From Godhood

If you just installed Evennia, your very first player account is called user #1, also known as the superuser or user. This user is very powerful, so powerful that it will override many game restrictions such as locks. This can be useful, but it also hides some functionality that you might want to test.

73 Evennia Documentation, Release 0.9

To temporarily step down from your superuser position you can use the quell command in-game:

quell

This will make you start using the permission of your current character’s level instead of your superuser level. If you didn’t change any settings your game Character should have an Developer level permission - high as can be without bypassing locks like the superuser does. This will work fine for the examples on this page. Use unquell to get back to superuser status again afterwards.

9.1.2 Creating an Object

Basic objects can be anything – swords, flowers and non-player characters. They are created using the create command:

create box

This created a new ‘box’ (of the default object type) in your inventory. Use the command inventory (or i) to see it. Now, ‘box’ is a rather short name, let’s rename it and tack on a few aliases.

name box= very large box;box;very;crate

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 all creation commands - you can always tag on a list of ;-separated aliases to the name of your new object. If you had wanted to not change the name itself, but to only add aliases, you could have 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

Hey presto - there it is on the ground, in all its normality.

examine box

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.

look box You see nothing special.

The description you get is not very exciting. Let’s add some flavor.

describe box= This is a large and very heavy box.

If you try the get command we will pick up the box. So far so good, but if we really want this to be a large and heavy box, people should not be able to run off with it that easily. To prevent this we need to lock it down. This is done by assigning a Lock to it. Make sure the box was dropped in the room, then try this:

lock box= get:false()

Locks represent a rather big topic, 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:

74 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

> get box You can't get that.

Think thís default error message looks dull? The get command looks for an Attribute named get_err_msg for returning a nicer error message (we just happen to know this, you would need to peek into the code 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.

Try to get it now and you should see a nicer error message echoed back to you. You create new Commands (or modify existing ones) in Python outside the game. See the Adding Commands tutorial for help with creating your first own Command.

9.1.3 Get a Personality

Scripts are powerful out-of-character objects useful for many “under the hood” things. One of their optional abilities is to do things on a timer. To try out a first script, let’s put one on ourselves. There is an example script in evennia/ contrib/tutorial_examples/bodyfunctions.py that is called BodyFunctions. To add this to us we will use the script command: script self= tutorial_examples.bodyfunctions.BodyFunctions

(note that you don’t have to give the full path as long as you are pointing to a place inside the contrib directory, it’s one of the places Evennia looks for Scripts). Wait a while and you will notice yourself starting making random observations. 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 particular script has a randomizer to determine if it will say something or not. So you will not see output every time it fires. 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 page explains more details.

9.1.4 Pushing Your Buttons

If we get back to the box we made, there is only so much fun you can do 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, Scripts and object-based Commands, you could expand it and other items to be as unique, complex and interactive as you want. Let’s take an example. So far we have only created objects that use the default object typeclass named simply Object. Let’s create an object that is a little more interesting. Under evennia/contrib/tutorial_examples there is a module red_button.py. It contains the enigmatic RedButton typeclass. Let’s make us one of those! create/drop button:tutorial_examples.red_button.RedButton

9.1. Building Quickstart 75 Evennia Documentation, Release 0.9

We import the RedButton python class the same way you would import it in Python except Evennia makes sure to look inevennia/contrib/ so you don’t have to write the full path every time. 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 and Commands controlling it are inside evennia/contrib/tutorial_examples/. If you wait for a while (make sure you dropped it!) the button will blink invitingly. Why don’t you try to push it . . . ? Surely a big red button is meant to be pushed. You know you want to.

9.1.5 Making Yourself a House

The main command for shaping the game world is dig. For example, if you are standing in Limbo you can dig a route to your new house location like this:

dig house= large red door;door; in,to the outside;out

This will create a new room named ‘house’. Spaces at the start/end of names and aliases are ignored so you could put more air if you wanted. This call will directly create an exit from your current location named ‘large red door’ and a corresponding exit named ‘to the outside’ in the house room leading back to Limbo. We also define a few aliases to those exits, so people don’t have to write the full thing all the time. If you wanted to use normal compass directions (north, west, southwest etc), you could do that with dig too. But Evennia also has a limited version of dig that helps for compass directions (and also up/down and in/out). It’s called tunnel:

tunnel sw= cliff

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:

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. You can also give its database (#dbref) number, which is unique to every object. This can be found with the examine command or by looking at the latest constructions with objects. Follow the north exit to your ‘house’ or teleport to it:

north

or:

teleport house

To manually open an exit back to Limbo (if you didn’t do so with the dig command):

open door= limbo

(or give limbo’s dbref which is #2)

76 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

9.1.6 Reshuffling the World

You can find things using the find command. Assuming you are back at Limbo, let’s teleport the large box to our house.

> teleport box= house very large box is leaving Limbo, heading for house. Teleported very large box-> house.

We can still find the box by using find:

> find box One Match(#1-#8): very large box(#8) - src.objects.objects.Object

Knowing the #dbref of the box (#8 in this example), you can grab the box and get it back here without actually yourself going to house first:

teleport #8 = here

(You can usually use here to refer to your current location. To refer to yourself you can use self or me). The box should now be back in Limbo with you. We are getting tired of the box. Let’s destroy it.

destroy box

You can destroy many objects in one go by giving a -separated list of objects (or their #dbrefs, if they are not in the same location) to the command.

9.1.7 Adding a Help Entry

An important part of building is keeping the help files updated. You can add, delete and append to existing help entries using the sethelp command.

sethelp/add MyTopic= This help topic is about...

9.1.8 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. First you need to switch back to superuser by using the unquell command. Next, place yourself in Limbo and run the following command:

batchcommand tutorial_world.build

This will take a while (be patient and don’t re-run the command). You will see all the commands used to build the world scroll by as the world is built for you. You will end up with a new exit from Limbo named tutorial. Apart from being a little solo-adventure in its own right, the tutorial world is a good source for learning Evennia building (and coding). Read the batch file to see exactly how it’s built, step by step. See also more info about the tutorial world here.

9.1. Building Quickstart 77 Evennia Documentation, Release 0.9

9.2 TextTags

This documentation details the various text tags supported by Evennia, namely colours, command links and inline functions. There is also an Understanding Color Tags tutorial which expands on the use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context.

9.2.1 Coloured text

Note that the Documentation does not display colour the way it would look on the screen. Color can be a very useful tool for your game. It can be used to increase readability and make your game more appealing visually. Remember however that, with the exception of the webclient, you generally don’t control the client used to connect to the game. There is, for example, one special tag meaning “yellow”. But exactly which hue of yellow is actually displayed on the user’s screen depends on the settings of their particular mud client. They could even swap the colours around or turn them off altogether if so desired. Some clients don’t even support color - text games are also played with special reading equipment by people who are blind or have otherwise diminished eyesight. So a good rule of thumb is to use colour to enhance your game but don’t rely on it to display critical information. If you are coding the game, you can add functionality to let users disable colours as they please, as described here. To see which colours your client support, use the default @color command. This will list all available colours for ANSI and Xterm256 along with the codes you use for them. You can find a list of all the parsed ANSI-colour codes in evennia/utils/ansi.py.

ANSI colours

Evennia supports the ANSI standard for text. This is by far the most supported MUD-color standard, available in all but the most ancient mud clients. The ANSI colours are red, green, yellow, blue, magenta, cyan, white and black. They are abbreviated by their first letter except for black which is abbreviated with the letter x. In ANSI there are “bright” and “normal” (darker) versions of each color, adding up to a total of 16 colours to use for foreground text. There are also 8 “background” colours. These have no bright alternative in ANSI (but Evennia uses the Xterm256 extension behind the scenes to offer them anyway). To colour your text you put special tags in it. Evennia will parse these and convert them to the correct markup for the client used. If the user’s client/console/display supports ANSI colour, they will see the text in the specified colour, otherwise the tags will be stripped (uncolored text). This works also for non-terminal clients, such as the webclient. For the webclient, Evennia will translate the codes to HTML RGB colors. Here is an example of the tags in action:

|rThis text is bright red.|n This is normal text. |RThis is a dark red text.|n This is normal text. |[rThis text has red background.|n This is normal text. ||[yThis is bright blue text on yellow background.|n This is normal text.

• |n - this tag will turn off all color formatting, including background colors. • |#- markup marks the start of foreground color. The case defines if the text is “bright” or “normal”. So |g is a bright green and |G is “normal” (darker) green. • |[# is used to add a background colour to the text. The case again specifies if it is “bright” or “normal”, so |[c starts a bright cyan background and |[C a darker cyan background.

78 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

• |!# is used to add foreground color without any enforced brightness/normal information. These are normal- intensity and are thus always given as uppercase, such as |! for red. The difference between e.g. |!R and |R is that |!R will “inherit” the brightness setting from previously set color tags, whereas |R will always reset to the normal-intensity red. The |# format contains an implicit |h/|H tag in it: disabling highlighting when switching to a normal color, and enabling it for bright ones. So |btest |!Rtest2 will result in a bright red test2 since the brightness setting from |b “bleeds over”. You could use this to for example quickly switch the intensity of a multitude of color tags. There is no background-color equivalent to |! style tags. • |h is used to make any following foreground ANSI colors bright (it has no effect on Xterm colors). This is only relevant to use with |! type tags and will be valid until the next |n, |H or normal (upper-case) |# tag. This tag will never affect background colors, those have to be set bright/normal explicitly. Technically, |h|!G is identical to |g. • |H negates the effects |h and returns all ANSI foreground colors (|! and | types) to ‘normal’ intensity. It has no effect on background and Xterm colors. Note: The ANSI standard does not actually support bright backgrounds like |[r - the standard only supports “normal” intensity backgrounds. To get around this Evennia instead implements these as Xterm256 colours behind the scenes. If the client does not support Xterm256 the ANSI colors will be used instead and there will be no visible difference between using upper- and lower-case background tags. If you want to display an ANSI marker as output text (without having any effect), you need to escape it by preceding its | with another |:

say The||r ANSI marker changes text color to bright red.

This will output the raw |r without any color change. This can also be necessary if you are doing ansi art that uses | with a letter directly following it. Use the command

@color ansi

to get a list of all supported ANSI colours and the tags used to produce them. A few additional ANSI codes are supported: • |/ A line break. You cannot put the normal Python \n line breaks in text entered inside the game (Evennia will filter this for security reasons). This is what you use instead: use the |/ marker to format text with line breaks from the game command line. • |- This will translate into a TAB character. This will not always show (or show differently) to the client since it depends on their local settings. It’s often better to use multiple spaces. • |_ This is a . You can usually use the normal space character, but if the space is at the end of the line, Evennia will likely crop it. This tag will not be cropped but always result in a space. • |* This will invert the current text/background colours. Can be useful to mark things (but see below).

Caveats of |*

The |* tag (inverse video) is an old ANSI standard and should usually not be used for more than to mark short snippets of text. If combined with other tags it comes with a series of potentially confusing behaviors: • The |* tag will only work once in a row:, ie: after using it once it won’t have an effect again until you declare another tag. This is an example:

9.2. TextTags 79 Evennia Documentation, Release 0.9

Normal text,| *reversed text|*, still reversed text.

that is, it will not reverse to normal at the second |*. You need to reset it manually:

``` Normal text, |*reversed text|n, normal again. ```

• The |* tag does not take “bright” colors into account:

|RNormal red,|hnow brightened.| *BG is normal red.

So |* only considers the ‘true’ foreground color, ignoring any highlighting. Think of the bright state (|h) as something like like in HTML: it modifies the appearance of a normal foreground color to match its bright counterpart, without changing its normal color. • Finally, after a |*, if the previous background was set to a dark color (via |[), |!#) will actually change the background color instead of the foreground:

|*reversed text |!R now BG is red.

For a detailed explanation of these caveats, see the Understanding Color Tags tutorial. But most of the time you might be better off to simply avoid |* and mark your text manually instead.

Xterm256 Colours

The Xterm256 standard is a colour scheme that supports 256 colours for text and/or background. While this offers many more possibilities than traditional ANSI colours, be wary that too many text colors will be confusing to the eye. Also, not all clients support Xterm256 - these will instead see the closest equivalent ANSI color. You can mix Xterm256 tags with ANSI tags as you please.

|555 This is pure white text.|n This is normal text. |230 This is olive green text. |[300 This text has a dark red background. |005|[054 This is dark blue text on a bright cyan background. |=a This is a greyscale value, equal to black. |=m This is a greyscale value, midway between white and black. |= This is a greyscale value, equal to white. |[=m This is a background greyscale value.

• |### - markup consists of three digits, each an integer from 0 to 5. The three digits describe the amount of red, green and blue (RGB) components used in the colour. So |500 means maximum red and none of the other colours - the result is a bright red. |520 is red with a touch of green - the result is orange. As opposed to ANSI colors, Xterm256 syntax does not worry about bright/normal intensity, a brighter (lighter) color is just achieved by upping all RGB values with the same amount. • |[### - this works the same way but produces a coloured background. • |=# - markup produces the xterm256 gray scale tones, where # is a letter from a (black) to z (white). This offers many more nuances of gray than the normal |### markup (which only has four gray tones between solid black and white (|000, |111, |222, |333 and |444)). • |[=# - this works in the same way but produces background gray scale tones. If you have a client that supports Xterm256, you can use

80 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

@color xterm256

to get a table of all the 256 colours and the codes that produce them. If the table looks broken up into a few blocks of colors, it means Xterm256 is not supported and ANSI are used as a replacement. You can use the @options command to see if xterm256 is active for you. This depends on if your client told Evennia what it supports - if not, and you know what your client supports, you may have to activate some features manually.

9.2.2 Clickable links

Evennia supports clickable links for clients that supports it. This marks certain text so it can be clicked by a mouse and trigger a given Evennia command. To support clickable links, Evennia requires the webclient or an third-party telnet client with MXP support (Note: Evennia only supports clickable links, no other MXP features). • |lc to start the link, by defining the command to execute. • |lt to continue with the text to show to the user (the link text). • |le to end the link text and the link definition. All elements must appear in exactly this order to make a valid link. For example,

"If you go |lcnorth|ltto the north|le you will find a cottage."

This will display as “If you go to the north you will find a cottage.” where clicking the link will execute the command north. If the client does not support clickable links, only the link text will be shown.

9.2.3 Inline functions

Note: Inlinefuncs are not activated by default. To use them you need to add INLINEFUNC_ENABLED=True to your settings file. Evennia has its own inline text formatting language, known as inlinefuncs. It allows the builder to include special function calls in code. They are executed dynamically by each session that receives them. To add an inlinefunc, you embed it in a text string like this:

"A normal string with $funcname(arg, arg, ...) embedded inside it."

When this string is sent to a session (with the msg() method), these embedded inlinefuncs will be parsed. Their return value (which always is a string) replace their call location in the finalized string. The interesting thing with this is that the function called will have access to which session is seeing the string, meaning the string can end up looking different depending on who is looking. It could of course also vary depending on other factors like game time. Any number of comma-separated arguments can be given (or none). No keywords are supported. You can also nest inlinefuncs by letting an argument itself also be another $funcname(arg, arg, ...) call (down to any depth of nesting). Function call resolution happens as in all programming languages inside-out, with the nested calls replacing the argument with their return strings before calling he parent.

> say"This is $pad(a center-padded text, 30,c,-) of width 30." You say,"This is ---- a center-padded text----- of width 30."

A special case happens if wanting to use an inlinefunc argument that itself includes a comma - this would be parsed as an argument separator. To escape you can either escape each comma manually with a \,, or you can embed the entire string in python triple-quotes """ or ''' - this will escape the entire argument, including commas and any nested inlinefunc calls within.

9.2. TextTags 81 Evennia Documentation, Release 0.9

Only certain functions are available to use as inlinefuncs and the game developer may add their own functions as needed.

New inlinefuncs

To add new inlinefuncs, edit the file mygame/server/conf/inlinefuncs.py. All globally defined functions in this module are considered inline functions by the system. The only exception is functions whose name starts with an _. An inlinefunc must be of the following form: def funcname(*args, **kwargs): # ... return modified_text where *args denotes all the arguments this function will accept as an $inlinefunc. The inline function is expected to clean arguments and check that they are valid. If needed arguments are not given, default values should be used. The function should always return a string (even if it’s empty). An inlinefunc should never cause a traceback regardless of the input (but it could log errors if desired). Note that whereas the function should accept **kwargs, keyword inputs are not usable in the call to the inlinefunc- tion. The kwargs part is instead intended for Evennia to be able to supply extra information. Currently Evennia sends a single keyword to every inline function and that is session, which holds the serversession this text is targeted at. Through the session object, a lot of dynamic possibilities are opened up for your inline functions. The settings.INLINEFUNC_MODULES configuration option is a list that decides which modules should be parsed for inline function definitions. This will include mygame/server/conf/inlinefuncs.py but more could be added. The list is read from left to right so if you want to overload default functions you just have to put your custom module-paths later in the list and name your functions the same as default ones. Here is an example, the crop default inlinefunction: from evennia.utils import utils def crop(*args, **kwargs): """ Inlinefunc. Crops ingoing text to given widths. Args: text (str, optional): Text to crop. width (str, optional): Will be converted to an integer. Width of crop in characters. suffix (str, optional): End string to mark the fact that a part of the string was cropped. Defaults to `[...]`. Kwargs: session (Session): Session performing the crop. Example: `$crop(text, 50, [...])`

""" text, width, suffix="", 78,"[...]" nargs= len(args) if nargs>0: text= args[0] if nargs>1: width= int(args[1]) if args[1].strip().isdigit() else 78 if nargs>2: suffix= args[2] return utils.crop(text, width=width, suffix=suffix)

82 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

Another example, making use of the Session:

def charactername(*args, **kwargs): """ Inserts the character name of whomever sees the string (so everyone will see their own name). Uses the account name for OOC communications.

Example: say "This means YOU, $charactername()!"

""" session= kwargs["session"] if session.puppet: return kwargs["session"].puppet.key else: return session.account.key

Evennia itself offers the following default inline functions (mostly as examples): • crop(text, width, suffix) - See above. • pad(text, width, align, fillchar) - this pads the text to width (default 78), alignment (“c”, “l” or “r”, defaulting to “c”) and fill-in character (defaults to space). Example: $pad(40,l,-) • clr(startclr, text, endclr) - A programmatic way to enter colored text for those who don’t want to use the normal |c type color markers for some reason. The color argument is the same as the color markers except without the actual pre-marker, so |r would be just r. If endclr is not given, it defaults to resetting the color (n). Example: $clr(b, A blue text) • space(number) - Inserts the given number of spaces. If no argument is given, use 4 spaces.

9.3 Building Permissions

OBS: This gives only a brief introduction to the access system. Locks and permissions are fully detailed here.

9.3.1 The super user

There are strictly speaking two types of users in Evennia, the super user and everyone else. The superuser is the first user you create, object #1. This is the all-powerful server-owner account. Technically the superuser not only has access to everything, it bypasses the permission checks entirely. This makes the superuser impossible to lock out, but makes it unsuitable to actually play-test the game’s locks and restrictions with (see @quell below). Usually there is no need to have but one superuser.

9.3.2 Assigning permissions

Whereas permissions can be used for anything, those put in settings.PERMISSION_HIERARCHY will have a ranking relative each other as well. We refer to these types of permissions as hierarchical permissions. When building locks to check these permissions, the perm() lock function is used. By default Evennia creates the following hierarchy (spelled exactly like this): 1. Developers basically have the same access as superusers except that they do not sidestep the Permission system. Assign only to really trusted server-admin staff since this level gives access both to server reload/shutdown functionality as well as (and this may be more critical) gives access to the all-powerful @py command that allows the execution of arbitrary Python code on the command line.

9.3. Building Permissions 83 Evennia Documentation, Release 0.9

2. Admins can do everything except affecting the server functions themselves. So an Admin couldn’t reload or shutdown the server for example. They also cannot execute arbitrary Python code on the console or import files from the hard drive. 3. Builders - have all the build commands, but cannot affect other accounts or mess with the server. 4. Helpers are almost like a normal Player, but they can also add help files to the database. 5. Players is the default group that new players end up in. A new player have permission to use tells and to use and create new channels. A user having a certain level of permission automatically have access to locks specifying access of a lower level. To assign a new permission from inside the game, you need to be able to use the @perm command. This is an Developer-level command, but it could in principle be made lower-access since it only allows assignments equal or lower to your current level (so you cannot use it to escalate your own permission level). So, assuming you yourself have Developer access (or is superuser), you assign a new account “Tommy” to your core staff with the command

@perm/account Tommy= Developer

or

@perm *Tommy= Developer

We use a switch or the *name format to make sure to put the permission on the Account and not on any eventual Character that may also be named “Tommy”. This is usually what you want since the Account will then remain an Developer regardless of which Character they are currently controlling. To limit permission to a per-Character level you should instead use quelling (see below). Normally permissions can be any string, but for these special hierarchical permissions you can also use plural (“Developer” and “Developers” both grant the same powers).

9.3.3 Quelling your permissions

When developing it can be useful to check just how things would look had your permission-level been lower. For this you can use quelling. Normally, when you puppet a Character you are using your Account-level permission. So even if your Character only has Accounts level permissions, your Developer-level Account will take precedence. With the @quell command you can change so that the Character’s permission takes precedence instead:

@quell

This will allow you to test out the game using the current Character’s permission level. A developer or builder can thus in principle maintain several test characters, all using different permission levels. Note that you cannot escalate your permissions this way; If the Character happens to have a higher permission level than the Account, the Account’s (lower) permission will still be used.

9.4 Connection Screen

When you first connect to your game you are greeted by Evennia’s default connection screen.

======Welcome to Evennia, version Beta-ra4d24e8a3cab+!

If you have an existing account, connect to it by typing: connect If you need to create an account, type (without the <>'s): create (continues on next page)

84 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

(continued from previous page)

If you have spaces in your username, enclose it in quotes. Enter help for more info. look will re-show this screen. ======

Effective, but not very exciting. You will most likely want to change this to be more unique for your game. This is simple: 1. Edit mygame/server/conf/connection_screens.py. 2. Reload Evennia.

Evennia will look into this module and locate all globally defined strings in it. These strings are used as the text in your connection screen and are shown to the user at startup. If more than one such string/screen is defined in the module, a random screen will be picked from among those available.

9.4.1 Commands available at the Connection Screen

You can also customize the Commands available to use while the connection screen is shown (connect, create etc). These commands are a bit special since when the screen is running the account is not yet logged in. A command is made available at the login screen by adding them to UnloggedinCmdSet in mygame/commands/default_cmdset.py. See Commands and the tutorial section on how to add new commands to a default command set.

9.5 Batch Processors

Building a game world is a lot of work, especially when starting out. Rooms should be created, descriptions have to be written, objects must be detailed and placed in their proper places. In many traditional MUD setups you had to do all this online, line by line, over a telnet session. Evennia already moves away from much of this by shifting the main coding work to external Python modules. But also building would be helped if one could do some or all of it externally. Enter Evennia’s batch processors (there are two of them). The processors allows you, as a game admin, to build your game completely offline in normal text files (batch files) that the processors understands. Then, when you are ready, you use the processors to read it all into Evennia (and into the database) in one go. You can of course still build completely online should you want to - this is certainly the easiest way to go when learning and for small build projects. But for major building work, the advantages of using the batch-processors are many: • It’s hard to compete with the comfort of a modern desktop text editor; Compared to a traditional MUD line input, you can get much better overview and many more features. Also, accidentally pressing Return won’t immediately commit things to the database. • You might run external spell checkers on your batch files. In the case of one of the batch-processors (the one that deals with Python code), you could also run external debuggers and code analyzers on your file to catch problems before feeding it to Evennia. • The batch files (as long as you keep them) are records of your work. They make a natural starting point for quickly re-building your world should you ever decide to start over.

9.5. Batch Processors 85 Evennia Documentation, Release 0.9

• If you are an Evennia developer, using a batch file is a fast way to setup a test-game after having reset the database. • The batch files might come in useful should you ever decide to distribute all or part of your world to others. There are two batch processors, the Batch-command processor and the Batch-code processor. The first one is the simpler of the two. It doesn’t require any programming knowledge - you basically just list in-game commands in a text file. The code-processor on the other hand is much more powerful but also more complex - it lets you use Evennia’s API to code your world in full-fledged Python code. • The Batch Command Processor • The Batch Code Processor If you plan to use international characters in your batchfiles you are wise to read about file encodings below.

9.5.1 A note on File Encodings

As mentioned, both the processors take text files as input and then proceed to process them. As long as you stick to the standard ASCII character set (which means the normal English characters, basically) you should not have to worry much about this section. Many languages however use characters outside the simple ASCII table. Common examples are various and umlauts but also completely different symbols like those of the greek or cyrillic alphabets. First, we should make it clear that Evennia itself handles international characters just fine. It (and Django) uses unicode strings internally. The problem is that when reading a text file like the batchfile, we need to know how to decode the byte-data stored therein to universal unicode. That means we need an encoding (a mapping) for how the file stores its data. There are many, many byte-encodings used around the world, with opaque names such as Latin-1, ISO-8859-3 or ARMSCII-8 to pick just a few examples. Problem is that it’s practially impossible to determine which encoding was used to save a file just by looking at it (it’s just a bunch of bytes!). You have to know. With this little introduction it should be clear that Evennia can’t guess but has to assume an encoding when trying to load a batchfile. The text editor and Evennia must speak the same “language” so to speak. Evennia will by default first try the international UTF-8 encoding, but you can have Evennia try any sequence of different encodings by customizing the ENCODINGS list in your settings file. Evennia will use the first encoding in the list that do not raise any errors. Only if none work will the server give up and return an error message. You can often change the text editor encoding (this depends on your editor though), otherwise you need to add the editor’s encoding to Evennia’s ENCODINGS list. If you are unsure, write a test file with lots of non-ASCII letters in the editor of your choice, then import to make sure it works as it should. More help with encodings can be found in the entry Text Encodings and also in the Wikipedia article here. A footnote for the batch-code processor: Just because Evennia can parse your file and your fancy special characters, doesn’t mean that Python allows their use. Python syntax only allows international characters inside strings. In all other source code only ASCII set characters are allowed.

9.6 Batch Command Processor

For an introduction and motivation to using batch processors, see here. This page describes the Batch-command processor. The Batch-code one is covered here.

86 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

9.6.1 Basic Usage

The batch-command processor is a superuser-only function, invoked by

> @batchcommand path.to.batchcmdfile

Where path.to.batchcmdfile is the path to a batch-command file with the “.ev” file ending. This path is given like a python path relative to a folder you define to hold your batch files, set with BATCH_IMPORT_PATH in your settings. Default folder is (assuming your game is in the mygame folder) mygame/world. So if you want to run the example batch file in mygame/world/batch_cmds.ev, you could use

> @batchcommand batch_cmds

A batch-command file contains a list of Evennia in-game commands separated by comments. The processor will run the batch file from beginning to end. Note that it will not stop if commands in it fail (there is no universal way for the processor to know what a failure looks like for all different commands). So keep a close watch on the output, or use Interactive mode (see below) to run the file in a more controlled, gradual manner.

9.6.2 The batch file

The batch file is a simple plain-text file containing Evennia commands. Just like you would write them in-game, except you have more freedom with line breaks. Here are the rules of syntax of an *.ev file. You’ll find it’s really, really simple: • All lines having the # (hash)-symbol as the first one on the line are considered comments. All non-comment lines are treated as a command and/or their arguments. • Comment lines have an actual function – they mark the end of the previous command definition. So never put two commands directly after one another in the file - separate them with a comment, or the second of the two will be considered an argument to the first one. Besides, using plenty of comments is good practice anyway. • A line that starts with the word #INSERT is a comment line but also signifies a special instruction. The syntax is #INSERT and tries to import a given batch-cmd file into this one. The inserted batch file (file ending .ev) will run normally from the point of the #INSERT instruction. • Extra whitespace in a command definition is ignored. - A completely empty line translates in to a line break in texts. Two empty lines thus means a new paragraph (this is obviously only relevant for commands accepting such formatting, such as the @desc command). • The very last command in the file is not required to end with a comment. • You cannot nest another @batchcommand statement into your batch file. If you want to link many batch-files together, use the #INSERT batch instruction instead. You also cannot launch the @batchcode command from your batch file, the two batch processors are not compatible. Below is a version of the example file found in evennia/contrib/tutorial_examples/batch_cmds.ev.

# # This is an example batch build file for Evennia. #

# This creates a red button @create button:tutorial_examples.red_button.RedButton # (This comment ends input for @create) # Next command. Let's create something. @set button/desc = This is a large red button. Now and then (continues on next page)

9.6. Batch Command Processor 87 Evennia Documentation, Release 0.9

(continued from previous page) it flashes in an evil, yet strangely tantalizing way.

A big sign sits next to it. It says:

------

Press me!

------

... It really begs to be pressed! You know you want to!

# This inserts the commands from another batch-cmd file named # batch_insert_file.ev. #INSERT examples.batch_insert_file

# (This ends the @set command). Note that single line breaks # and extra whitespace in the argument are ignored. Empty lines # translate into line breaks in the output. # Now let's place the button where it belongs (let's say limbo #2 is # the evil lair in our example) @teleport #2 # (This comments ends the @teleport command.) # Now we drop it so others can see it. # The very last command in the file needs not be ended with #. drop button

To test this, run @batchcommand on the file:

> @batchcommand contrib.tutorial_examples.batch_cmds

A button will be created, described and dropped in Limbo. All commands will be executed by the user calling the command. Note that if you interact with the button, you might find that its description changes, loosing your custom- set description above. This is just the way this particular object works.

9.6.3 Interactive mode

Interactive mode allows you to more step-wise control over how the batch file is executed. This is useful for debugging and also if you have a large batch file and is only updating a small part of it – running the entire file again would be a waste of time (and in the case of @create-ing objects you would to end up with multiple copies of same-named objects, for example). Use @batchcommand with the /interactive flag to enter interactive mode.

> @batchcommand/interactive tutorial_examples.batch_cmds

You will see this:

01/04: @create button:tutorial_examples.red_button.RedButton (hh for help)

This shows that you are on the @create command, the first out of only four commands in this batch file. Observe that the command @create has not been actually processed at this point!

88 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

To take a look at the full command you are about to run, use ll (a batch-processor version of look). Use pp to actually process the current command (this will actually @create the button) – and make sure it worked as planned. Use nn (next) to go to the next command. Use hh for a list of commands. If there are errors, fix them in the batch file, then use rr to reload the file. You will still be at the same command and can rerun it easily with pp as needed. This makes for a simple debug cycle. It also allows you to rerun individual troublesome commands - as mentioned, in a large batch file this can be very useful. Do note that in many cases, commands depend on the previous ones (e.g. if @create in the example above had failed, the following commands would have had nothing to operate on). Use nn and bb (next and back) to step through the file; e.g. nn 12 will jump 12 steps forward (without processing any command in between). All normal commands of Evennia should work too while working in interactive mode.

9.6.4 Limitations and Caveats

The batch-command processor is great for automating smaller builds or for testing new commands and objects re- peatedly without having to write so much. There are several caveats you have to be aware of when using the batch- command processor for building larger, complex worlds though. The main issue is that when you run a batch-command script you (you, as in your superuser character) are actually moving around in the game creating and building rooms in sequence, just as if you had been entering those commands manually, one by one. You have to take this into account when creating the file, so that you can ‘walk’ (or teleport) to the right places in order. This also means there are several pitfalls when designing and adding certain types of objects. Here are some examples: • Rooms that change your ‘Command Set‘_: Imagine that you build a ‘dark’ room, which severely limits the cmdsets of those entering it (maybe you have to find the light switch to proceed). In your batch script you would create this room, then teleport to it - and promptly be shifted into the dark state where none of your normal build commands work . . . • Auto-teleportation: Rooms that automatically teleport those that enter them to another place (like a trap room, for example). You would be teleported away too. • Mobiles: If you add aggressive mobs, they might attack you, drawing you into combat. If they have AI they might even follow you around when building - or they might move away from you before you’ve had time to finish describing and equipping them! The solution to all these is to plan ahead. Make sure that superusers are never affected by whatever effects are in play. Add an on/off switch to objects and make sure it’s always set to off upon creation. It’s all doable, one just needs to keep it in mind.

9.6.5 Assorted notes

The fact that you build as ‘yourself’ can also be considered an advantage however, should you ever decide to change the default command to allow others than superusers to call the processor. Since normal access-checks are still performed, a malevolent builder with access to the processor should not be able to do all that much damage (this is the main drawback of the Batch Code Processor) • GNU Emacs users might find it interesting to use emacs’ evennia mode. This is an Emacs major mode found in evennia/utils/evennia-mode.el. It offers correct syntax highlighting and indentation with when editing .ev files in Emacs. See the header of that file for installation instructions. • VIM users can use amfl’s vim-evennia mode instead, see its readme for install instructions.

9.6. Batch Command Processor 89 Evennia Documentation, Release 0.9

9.7 Batch Code Processor

For an introduction and motivation to using batch processors, see here. This page describes the Batch-code processor. The Batch-command one is covered here.

9.7.1 Basic Usage

The batch-code processor is a superuser-only function, invoked by

> @batchcode path.to.batchcodefile

Where path.to.batchcodefile is the path to a batch-code file. Such a file should have a name ending in “.py” (but you shouldn’t include that in the path). The path is given like a python path relative to a folder you define to hold your batch files, set by BATCH_IMPORT_PATH in your settings. Default folder is (assuming your game is called “mygame”) mygame/world/. So if you want to run the example batch file in mygame/world/batch_code. py, you could simply use

> @batchcode batch_code

This will try to run through the entire batch file in one go. For more gradual, interactive control you can use the /interactive switch. The switch /debug will put the processor in debug mode. Read below for more info.

9.7.2 The batch file

A batch-code file is a normal Python file. The difference is that since the batch processor loads and executes the file rather than importing it, you can reliably update the file, then call it again, over and over and see your changes without needing to @reload the server. This makes for easy testing. In the batch-code file you have also access to the following global variables: • caller - This is a reference to the object running the batchprocessor. • DEBUG - This is a boolean that lets you determine if this file is currently being run in debug-mode or not. See below how this can be useful. Running a plain Python file through the processor will just execute the file from beginning to end. If you want to get more control over the execution you can use the processor’s interactive mode. This runs certain code blocks on their own, rerunning only that part until you are happy with it. In order to do this you need to add special markers to your file to divide it up into smaller chunks. These take the form of comments, so the file remains valid Python. Here are the rules of syntax of the batch-code *.py file. • #CODE as the first on a line marks the start of a code block. It will last until the beginning of another marker or the end of the file. Code blocks contain functional python code. Each #CODE block will be run in complete isolation from other parts of the file, so make sure it’s self-contained. • #HEADER as the first on a line marks the start of a header block. It lasts until the next marker or the end of the file. This is intended to hold imports and variables you will need for all other blocks .All python code defined in a header block will always be inserted at the top of every #CODE blocks in the file. You may have more than one #HEADER block, but that is equivalent to having one big one. Note that you can’t exchange data between code blocks, so editing a header-variable in one code block won’t affect that variable in any other code block! • #INSERT path.to.file will insert another batchcode (Python) file at that position. •A # that is not starting a #HEADER, #CODE or #INSERT instruction is considered a comment. • Inside a block, normal Python syntax rules apply. For the sake of indentation, each block acts as a separate python module.

90 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

Below is a version of the example file found in evennia/contrib/tutorial_examples/.

# # This is an example batch-code build file for Evennia. #

#HEADER

# This will be included in all other #CODE blocks

from evennia import create_object, search_object from evennia.contrib.tutorial_examples import red_button from typeclasses.objects import Object limbo= search_object('Limbo')[0]

#CODE red_button= create_object(red_button.RedButton, key="Red button", location=limbo, aliases=["button"])

# caller points to the one running the script caller.msg("A red button was created.")

# importing more code from another batch-code file #INSERT batch_code_insert

#CODE table= create_object(Object, key="Blue Table", location=limbo) chair= create_object(Object, key="Blue Chair", location=limbo) string="A %s and %s were created." if DEBUG: table.delete() chair.delete() string+=" Since debug was active,"\ "they were deleted again." caller.msg(string% (table, chair))

This uses Evennia’s Python API to create three objects in sequence.

9.7.3 Debug mode

Try to run the example script with

> @batchcode/debug tutorial_examples.example_batch_code

The batch script will run to the end and tell you it completed. You will also get messages that the button and the two pieces of furniture were created. Look around and you should see the button there. But you won’t see any chair nor a table! This is because we ran this with the /debug switch, which is directly visible as DEBUG==True inside the script. In the above example we handled this state by deleting the chair and table again. The debug mode is intended to be used when you test out a batchscript. Maybe you are looking for bugs in your code or try to see if things behave as they should. Running the script over and over would then create an ever-growing stack of chairs and tables, all with the same name. You would have to go back and painstakingly delete them later.

9.7. Batch Code Processor 91 Evennia Documentation, Release 0.9

9.7.4 Interactive mode

Interactive mode works very similar to the batch-command processor counterpart. It allows you more step-wise control over how the batch file is executed. This is useful for debugging or for picking and choosing only particular blocks to run. Use @batchcode with the /interactive flag to enter interactive mode.

> @batchcode/interactive tutorial_examples.batch_code

You should see the following:

01/02: red_button= create_object(red_button.RedButton, [...] (hh for help)

This shows that you are on the first #CODE block, the first of only two commands in this batch file. Observe that the block has not actually been executed at this point! To take a look at the full code snippet you are about to run, use ll (a batch-processor version of look).

from evennia.utils import create, search from evennia.contrib.tutorial_examples import red_button from typeclasses.objects import Object limbo= search.objects(caller,'Limbo', global_search= True)[0]

red_button= create.create_object(red_button.RedButton, key="Red button", location=limbo, aliases=["button"])

# caller points to the one running the script caller.msg("A red button was created.")

Compare with the example code given earlier. Notice how the content of #HEADER has been pasted at the top of the #CODE block. Use pp to actually execute this block (this will create the button and give you a message). Use nn (next) to go to the next command. Use hh for a list of commands. If there are tracebacks, fix them in the batch file, then use rr to reload the file. You will still be at the same code block and can rerun it easily with pp as needed. This makes for a simple debug cycle. It also allows you to rerun individual troublesome blocks - as mentioned, in a large batch file this can be very useful (don’t forget the /debug mode either). Use nn and bb (next and back) to step through the file; e.g. nn 12 will jump 12 steps forward (without processing any blocks in between). All normal commands of Evennia should work too while working in interactive mode.

9.7.5 Limitations and Caveats

The batch-code processor is by far the most flexible way to build a world in Evennia. There are however some caveats you need to keep in mind.

Safety

Or rather the lack of it. There is a reason only superusers are allowed to run the batch-code processor by default. The code-processor runs without any Evennia security checks and allows full access to Python. If an untrusted party could run the code-processor they could execute arbitrary python code on your machine, which is potentially a very dangerous thing. If you want to allow other users to access the batch-code processor you should make sure to run Evennia as a separate and very limited-access user on your machine (i.e. in a ‘jail’). By comparison, the batch- command processor is much safer since the user running it is still ‘inside’ the game and can’t really do anything outside what the game commands allow them to.

92 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

No communication between code blocks

Global variables won’t work in code batch files, each block is executed as stand-alone environments. #HEADER blocks are literally pasted on top of each #CODE block so updating some header-variable in your block will not make that change available in another block. Whereas a python execution limitation, allowing this would also lead to very hard-to-debug code when using the interactive mode - this would be a classical example of “spaghetti code”. The main practical issue with this is when building e.g. a room in one code block and later want to connect that room with a room you built in the current block. There are two ways to do this: • Perform a database search for the name of the room you created (since you cannot know in advance which dbref it got assigned). The problem is that a name may not be unique (you may have a lot of “A dark forest” rooms). There is an easy way to handle this though - use Tags or Aliases. You can assign any number of tags and/or aliases to any object. Make sure that one of those tags or aliases is unique to the room (like “room56”) and you will henceforth be able to always uniquely search and find it later. • Use the caller global property as an inter-block storage. For example, you could have a dictionary of room references in an ndb:

#HEADER if caller.ndb.all_rooms is None: caller.ndb.all_rooms={}

#CODE # create and store the castle castle= create_object("rooms.Room", key="Castle") caller.ndb.all_rooms["castle"]= castle

#CODE # in another node we want to access the castle castle= caller.ndb.all_rooms.get("castle")

Note how we check in #HEADER if caller.ndb.all_rooms doesn’t already exist before creating the dict. Remember that #HEADER is copied in front of every #CODE block. Without that if statement we’d be wiping the dict every block!

Don’t treat a batchcode file like any Python file

Despite being a valid Python file, a batchcode file should only be run by the batchcode processor. You should not do things like define Typeclasses or Commands in them, or import them into other code. Importing a module in Python will execute base level of the module, which in the case of your average batchcode file could mean creating a lot of new objects every time.

Don’t let code rely on the batch-file’s real file path

When you import things into your batchcode file, don’t use relative imports but always import with paths starting from the root of your game directory or evennia library. Code that relies on the batch file’s “actual” location will fail. Batch code files are read as text and the strings executed. When the code runs it has no knowledge of what file those strings where once a part of.

9.8 Spawner

The spawner is a system for defining and creating individual objects from a base template called a

9.8. Spawner 93 Evennia Documentation, Release 0.9

prototype. It is only designed for use with in-game Objects, not any other type of entity.

The normal way to create a custom object in Evennia is to make a Typeclass. If you haven’t read up on Typeclasses yet, think of them as normal Python classes that save to the database behind the scenes. Say you wanted to create a “Goblin” enemy. A common way to do this would be to first create a Mobile typeclass that holds everything common to mobiles in the game, like generic AI, combat code and various movement methods. A Goblin subclass is then made to inherit from Mobile. The Goblin class adds stuff unique to goblins, like group-based AI (because goblins are smarter in a group), the ability to panic, dig for gold etc.

But now it’s time to actually start to create some goblins and put them in the world. What if we wanted those goblins to not all look the same? Maybe we want grey-skinned and green-skinned goblins or some goblins that can cast spells or which wield different weapons? We could make subclasses of Goblin, like GreySkinnedGoblin and GoblinWieldingClub. But that seems a bit excessive (and a lot of Python code for every little thing). Using classes can also become impractical when wanting to combine them - what if we want a grey-skinned goblin shaman wielding a spear - setting up a web of classes inheriting each other with multiple inheritance can be tricky.

This is what the prototype is for. It is a Python dictionary that describes these per-instance changes to an object. The prototype also has the advantage of allowing an in-game builder to customize an object without access to the Python backend. Evennia also allows for saving and searching prototypes so other builders can find and use (and tweak) them later. Having a library of interesting prototypes is a good reasource for builders. The OLC system allows for creating, saving, loading and manipulating prototypes using a menu system.

The spawner takes a prototype and uses it to create (spawn) new, custom objects.

9.8.1 Using the OLC

Enter the olc command or @spawn/olc to enter the prototype . This is a menu system for creating, loading, saving and manipulating prototypes. It’s intended to be used by in-game builders and will give a better understanding of prorotypes in general. Use help on each node of the menu for more information. Below are further details about how prototypes work and how they are used.

9.8.2 The prototype

The prototype dictionary defines all possible database-properties of an Object. It has a fixed set of allowed keys:

Prototype keys

All keys starting with prototype_ are for book keeping.

94 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

• prototype_key - the ‘name’ of the prototype. While this can sometimes be skipped (such as when defining a prototype in a module), it’s generally best to always include this. It is used for book-keeping and storing of the prototype so you can find it later. • prototype_parent - If given, this should be the prototype_key of another prototype stored in the system or available in a module. This makes this prototype inherit the keys from the parent and only override what is needed. Give a tuple (parent1, parent2, ...) for multiple left-right inheritance. • prototype_desc - this is optional and used when listing the prototype in in-game listings. • protototype_tags - this is optional and allows for tagging the prototype in order to find it easier later. • prototype_locks - two lock types are supported: edit and spawn. The first lock restricts the copying and editing of the prototype when loaded through the OLC. The second determines who may use the prototype to create new objects. The remaining keys determine actual aspects of the objects to spawn from this prototype: • key - the main object identifier. Defaults to “Spawned Object X”, where X is a random integer. • typeclass - python-path to the typeclass to use, if not set, will use settings. BASE_OBJECT_TYPECLASS. • location - this should be a #dbref. • home - a valid #dbref. Defaults to location or settings.DEFAULT_HOME if location does not exist. • destination - a valid #dbref. Only used by exits. • permissions - list of permission strings, like ["Accounts", "may_use_red_door"] • locks - a lock-string like "edit:all();control:perm(Builder)" • aliases - list of strings for use as aliases • tags - list Tags. These are given as tuples (tag, category, data). • attrs - list of Attributes. These are given as tuples (attrname, value, category, lockstring) • Any other keywords are interpreted as non-category Attributes and their values. This is convenient for simple Attributes - use attrs for full control of Attributes. Deprecated as of Evennia 0.8: • ndb_ - sets the value of a non-persistent attribute ("ndb_" is stripped from the name). This is simply not useful in a prototype and is deprecated. • exec - This accepts a code snippet or a list of code snippets to run. This should not be used - use callables or protfuncs instead (see below).

Prototype values

The prototype supports values of several different types. It can be a hard-coded valuei:

{"key":"An ugly goblin",...}

It can also be a callable. This callable is called without arguments whenever the prototype is used to spawn a new object:

9.8. Spawner 95 Evennia Documentation, Release 0.9

{"key": _get_a_random_goblin_name,...}

By use of Python lambda one can wrap the callable so as to make immediate settings in the prototype:

{"key": lambda: random.choice(("Urfgar","Rick the smelly","Blargh the foul",...)),

˓→...}

Finally, the value can be a prototype function (Protfunc). This is a callable embedded in a string in much the same way as an InlineFunc - they are actually parsed using the same parser. These work like callables, except what function can be used is strictly limited to those made available as global functions in one of the modules listed in settings.PROT_FUNC_MODULES. They thus allow for full control of the input/output, error handling etc.

{"key":"$choice(Urfgar, Rick the smelly, Blargh the foul)",...}

For developers with access to Python, using protfuncs in prototypes is generally not useful - passing real Python functions is a lot more powerful. Their main use is to allow in-game builders to do limited coding/scripting for their prototypes without giving them direct access raw Python.

9.8.3 Storing prototypes

Prototypes can be defined and stored in two ways • Stored as Scripts in the database. These are sometimes referred to as database-prototypes This is the only way for in-game builders to modify and add prototypes. • As dictionaries assigned to global variables in one of the modules defined in settings.PROTOTYPE_MODULES. Such modules are necessarily “read-only” from in-game and cannot be modified (but copies of them could be made into database-prototypes). They can be useful in order for developer to provide “starting” or “base” prototypes to build from.

# in a module Evennia looks at for prototypes, # (like mygame/server/conf/prototypes.py)

ORC_SHAMAN = {"key":"Orc shaman", "typeclass": "typeclasses.monsters.Orc", "weapon": "wooden staff", "health": 20}

| Note that in the example above, ``"ORC_SHAMAN"`` will become the ``prototype_key`` of this prototype. | It’s the only case when ``prototype_key`` can be skipped in a (continues on next page)

96 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

(continued from previous page) prototype. However, if ``prototype_key`` | was given explicitly, that would take precedence. This is a legacy behavior and it’s recommended | that you always add ``prototype_key`` explicitly to be consistent.

9.8.4 Using @spawn

The spawner can be used from inside the game through the Builder-only @spawn command. Assuming the “goblin” typeclass is available to the system (either as a database-prototype or read from module), you can spawn a new goblin with

@spawn goblin

You can also specify the prototype directly as a valid Python dictionary:

@spawn {"prototype_key":"shaman",\ "key":"Orc shaman",\ "prototype_parent":"goblin",\ "weapon":"wooden staff",\ "health": 20}

9.8.5 Using evennia.utils.spawner()

In code you access the spawner mechanism directly via the call new_objects= evennia.utils.spawner.spawn( *prototypes)

All arguments are prototype dictionaries. The function will return a matching list of created objects. Example:

obj1, obj2= evennia.utils.spawner.spawn({"key":"Obj1","desc":"A test"}, {"key":"Obj2","desc":"Another test"})

Note that no location will be set automatically when using evennia.utils.spawner.spawn(), you have to specify location explicitly in the prototype dict.

If the prototypes you supply are using parent keywords, the spawner will look to settings.PROTOTYPE_MODULES to determine which modules contain parents available to use. You can use the prototype_modules keyword to change the list of available parent modules only for this particular call. Finally, calling spawn(return_prototypes=True) will return a dictionary of all the available prototypes from all available modules. In this case, no objects will be created or returned - this is meant to be used for compiling help information for an end user.

9.8. Spawner 97 Evennia Documentation, Release 0.9

9.9 Tutorial World Introduction

The Tutorial World is a small and functioning MUD-style game world. It is intended to be deconstructed and used as a way to learn Evennia. 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. The source code is fully documented. You can find the whole thing in evennia/contrib/tutorial_world/. Some features exemplified by the tutorial world: • Tutorial command, giving “behind-the-scenes” help for every room and some of the special objects • Rooms with custom return_appearance to show details. • Hidden exits • Objects with multiple custom interactions • Large-area rooms • Outdoor weather rooms • Dark room, needing light source • Puzzle object • Multi-room puzzle • Aggressive mobile with roam, pursue and battle state-engine AI • Weapons, also used by mobs • Simple combat system with attack/defend commands • Object spawning • Teleporter trap rooms

9.9.1 Install

The tutorial world consists of a few modules in evennia/contrib/tutorial_world/ containing custom Typeclasses for rooms and objects and associated Commands. These reusable bits and pieces are then put together into a functioning game area (“world” is maybe too big a word for such a small ) using a batch script called build.ev. To install, log into the server as the superuser (user #1) and run:

@batchcommand tutorial_world.build

The world will be built (this might take a while, so don’t rerun the command even if it seems the system has frozen). After finishing you will end up back in Limbo with a new exit called tutorial. An alternative is

@batchcommand/interactive tutorial_world.build

with the /interactive switch you are able to step through the building process at your own pace to see what happens in detail. To play the tutorial “correctly”, you should not do so as superuser. The reason for this is that many game systems ignore the presence of a superuser and will thus not work as normal. Use the @quell command to limit your powers or log out and reconnect as a different user. As superuser you can of course examine things “under the hood” later if you want.

98 Chapter 9. Builder Documentation Evennia Documentation, Release 0.9

9.9.2 Gameplay

To get into the mood of this miniature quest, imagine you are an adventurer out to find fame and fortune. You have heard rumours of an old castle ruin by the coast. In its depth a warrior princess was buried together with her powerful magical weapon - a valuable prize, if it’s true. Of course this is a chance to adventure that you cannot turn down! You reach the ocean in the midst of a raging thunderstorm. With wind and rain screaming in your face you stand where the moor meets the sea along a high, rocky coast . . . • Look at everything. • 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 tutorial). • In order to fight, you need to first find some type of weapon. • slash is a normal attack • stab launches an attack that makes more damage but has a lower chance to hit. • defend will lower the chance to taking damage on your enemy’s next attack. • You can run from a fight that feels too deadly. Expect to be chased though. • Being defeated is a part of the experience . . .

9.9.3 Uninstall

Uninstalling the tutorial world basically means deleting all the rooms and objects it consists of. First, move out of the tutorial area.

@find tut#01 @find tut#16

This should locate the first and last rooms created by build.ev - Intro and Outro. If you installed normally, every- thing created between these two numbers should be part of the tutorial. Note their dbref numbers, for example 5 and 80. Next we just delete all objects in that range:

@del 5-80

You will see some errors since some objects are auto-deleted and so cannot be found when the delete mechanism gets to them. That’s fine. You should have removed the tutorial completely once the command finishes.

9.9.4 Notes

When reading and learning from the code, keep in mind that Tutorial World was created with a very specific goal: to install easily and to not permanently modify the rest of the server. It therefore goes to some length to use only temporary solutions and to clean up after itself.

9.9. Tutorial World Introduction 99 Evennia Documentation, Release 0.9

100 Chapter 9. Builder Documentation CHAPTER 10

Developer Basics

This chapter gives an introduction to coding with Evennia.

10.1 Coding Introduction

Evennia allows for a lot of freedom when designing your game - but to code efficiently you still need to adopt some best practices as well as find a good place to start to learn. Here are some pointers to get you going.

10.1.1 Python

Evennia is developed using Python. Even if you are more of a designer than a coder, it is wise to learn how to read and understand basic Python code. If you are new to Python, or need a refresher, take a look at our two-part Python introduction.

10.1.2 Explore Evennia interactively

When new to Evennia it can be hard to find things or figure out what is available. Evennia offers a special interactive python shell that allows you to experiment and try out things. It’s recommended to use ipython for this since the vanilla python prompt is very limited. Here are some simple commands to get started:

# [open a new console/terminal] # [activate your evennia virtualenv in this console/terminal] pip install ipython # [only needed the first time] cd mygame evennia shell

This will open an Evennia-aware python shell (using ipython). From within this shell, try

101 Evennia Documentation, Release 0.9

import evennia evennia.

That is, enter evennia. and press the key. This will show you all the resources made available at the top level of Evennia’s “flat API”. See the flat API page for more info on how to explore it efficiently. You can complement your exploration by peeking at the sections of the much more detailed Developer Central. The Tutorials section also contains a growing collection of system- or implementation-specific help.

10.1.3 Use a python syntax checker

Evennia works by importing your own modules and running them as part of the server. Whereas Evennia should just gracefully tell you what errors it finds, it can nevertheless be a good idea for you to check your code for simple syntax errors before you load it into the running server. There are many python syntax checkers out there. A fast and easy one is pyflakes, a more verbose one is pylint. You can also check so that your code looks up to snuff using pep8. Even with a syntax checker you will not be able to catch every possible problem - some bugs or problems will only appear when you actually run the code. But using such a checker can be a good start to weed out the simple problems.

10.1.4 Plan before you code

Before you start coding away at your dream game, take a look at our Game Planning page. It might hopefully help you avoid some common pitfalls and time sinks.

10.1.5 Code in your game folder, not in the evennia/ repository

As part of the Evennia setup you will create a game folder to host your game code. This is your home. You should never need to modify anything in the evennia library (anything you download from us, really). You import useful functionality from here and if you see code you like, copy&paste it out into your game folder and edit it there. If you find that Evennia doesn’t support some functionality you need, make a Feature Request about it. Same goes for bugs. If you add features or fix bugs yourself, please consider Contributing your changes upstream!

10.1.6 Learn to read tracebacks

Python is very good at reporting when and where things go wrong. A traceback shows everything you need to know about crashing code. The text can be pretty long, but you usually are only interested in the last bit, where it says what the error is and at which module and line number it happened - armed with this info you can resolve most problems. Evennia will usually not show the full traceback in-game though. Instead the server outputs errors to the termi- nal/console from which you started Evennia in the first place. If you want more to show in-game you can add IN_GAME_ERRORS = True to your settings file. This will echo most (but not all) tracebacks both in-game as well as to the terminal/console. This is a potential security problem though, so don’t keep this active when your game goes into production. A common confusing error is finding that objects in-game are suddenly of the type DefaultObject rather than your custom typeclass. This happens when you introduce a critical Syntax error to the module holding your custom class. Since such a module is not valid Python, Evennia can’t load it at all. Instead of crashing, Evennia will then print the full traceback to the terminal/console and temporarily fall back to the safe DefaultObject until you fix the problem and reload.

102 Chapter 10. Developer Basics Evennia Documentation, Release 0.9

10.1.7 Docs are here to help you

Some people find reading documentation extremely dull and shun it out of principle. That’s your call, but reading docs really does help you, promise! Evennia’s documentation is pretty thorough and knowing what is possible can often give you a lot of new cool game ideas. That said, if you can’t find the answer in the docs, don’t be shy to ask questions! The discussion group and the irc chat are also there for you.

10.1.8 The most important point

And finally, of course, have fun!

10.2 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

10.2.1 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 instance • evennia - Evennia’s flat 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.

10.2.2 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.

(continues on next page)

10.2. Execute Python Code 103 Evennia Documentation, Release 0.9

(continued from previous page) 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`_ 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).

10.2.3 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") <<<[]

(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") <<<[]

The managers are useful for all sorts of database studies.

@py ev.managers.configvalues.all() <<<[,,...]

104 Chapter 10. Developer Basics Evennia Documentation, Release 0.9

10.2.4 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. 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]: [, , ...]

See the page about the Evennia-API for more things to explore.

10.3 Quirks

This is a list of various quirks or common stumbling blocks that people often ask about or report when using (or trying to use) Evennia. They are not bugs.

10.3.1 Forgetting to use @reload to see changes to your typeclasses

Firstly: Reloading the server is a safe and usually quick operation which will not disconnect any accounts. New users tend to forget this step. When editing source code (such as when tweaking typeclasses and commands or adding new commands to command sets) you need to either use the in-game @reload command or, from the command line do python evennia.py reload before you see your changes.

10.3.2 Web admin to create new Account

If you use the default login system and are trying to use the Web admin to create a new Player account, you need to consider which MULTIACCOUNT_MODE you are in. If you are in MULTIACCOUNT_MODE 0 or 1, the login system expects each Account to also have a Character object named the same as the Account - there is no character creation screen by default. If using the normal mud login screen, a Character with the same name is automatically created and connected to your Account. From the web interface you must do this manually. So, when creating the Account, make sure to also create the Character from the same form as you create the Account from. This should set everything up for you. Otherwise you need to manually set the “account” property on the Character and the “character” property on the Account to point to each other. You must also set the lockstring of the Character to allow the Account to “puppet” this particular character.

10.3. Quirks 105 Evennia Documentation, Release 0.9

10.3.3 Mutable attributes and their connection to the database

When storing a mutable object (usually a list or a dictionary) in an Attribute object.db.mylist=[1,2,3] you should know that the connection to the database is retained also if you later extract that Attribute into another vari- able (what is stored and retrieved is actually a PackedList or a PackedDict that works just like their namesakes except they save themselves to the database when changed). So if you do alist= object.db.mylist alist.append(4) this updates the database behind the scenes, so both alist and object.db.mylist are now [1,2,3,4] If you don’t want this, Evennia provides a way to stably disconnect the mutable from the database by use of evennia. utils.dbserialize.deserialize: from evennia.utils.dbserialize import deserialize blist= deserialize(object.db.mylist) blist.append(4)

The property blist is now [1,2,3,4] whereas object.db.mylist remains unchanged. If you want to update the database you’d need to explicitly re-assign the updated data to the mylist Attribute.

10.3.4 Commands are matched by name or alias

When merging command sets it’s important to remember that command objects are identified both by key or alias. So if you have a command with a key look and an alias , introducing another command with a key ls will be assumed by the system to be identical to the first one. This usually means merging cmdsets will overload one of them depending on priority. Whereas this is logical once you know how command objects are handled, it may be confusing if you are just looking at the command strings thinking they are parsed as-is.

10.3.5 Objects turning to DefaultObject

A common confusing error for new developers is finding that one or more objects in-game are suddenly of the type DefaultObject rather than the typeclass you wanted it to be. This happens when you introduce a critical Syntax error to the module holding your custom class. Since such a module is not valid Python, Evennia can’t load it at all to get to the typeclasses within. To keep on running, Evennia will solve this by printing the full traceback to the terminal/console and temporarily fall back to the safe DefaultObject until you fix the problem and reload. Most errors of this kind will be caught by any good text editors. Keep an eye on the terminal/console during a reload to catch such errors - you may have to scroll up if your window is small.

10.3.6 Overriding of magic methods

Python implements a system of magic methods, usually prefixed and suffixed by double- (__example__) that allow object instances to have certain operations performed on them without needing to do things like turn them into strings or numbers first– for example, is obj1 greater than or equal to obj2? Neither object is a number, but given obj1.size == "small" and obj2.size == "large", how might one compare these two arbitrary English adjective strings to figure out which is greater than the other? By defining the __ge__ (greater than or equal to) magic method on the object class in which you figure out which word has

106 Chapter 10. Developer Basics Evennia Documentation, Release 0.9 greater significance, perhaps through use of a mapping table ({'small':0, 'large':10}) or other lookup and comparing the numeric values of each. Evennia extensively makes use of magic methods on typeclasses to do things like initialize objects, check object existence or iterate over objects in an inventory or container. If you override or interfere with the return values from the methods Evennia expects to be both present and working, it can result in very inconsistent and hard-to-diagnose errors. The moral of the story– it can be dangerous to tinker with magic methods on typeclassed objects. Try to avoid doing so.

10.3.7 Known upstream bugs

• There is currently (Autumn 2017) a bug in the zope.interface installer on some Linux Ubuntu distributions (notably Ubuntu 16.04 LTS). Zope is a dependency of Twisted. The error manifests in the server not starting with an error that zope.interface is not found even though pip list shows it’s installed. The reason is a missing empty __init__.py file at the root of the zope package. If the virtualenv is named “evenv” as suggested in the Getting Started instructions, use the following command to fix it:

touch evenv/local/lib/python2.7/site-packages/zope/__init__.py

This will create the missing file and things should henceforth work correctly.

10.4 Licensing

Evennia is licensed under the very friendly BSD (3-clause) license. You can find the license as LICENSE.txt in the Evennia repository’s root.

Q: When creating a game using Evennia, what does the license permit me to do with it?

A: It’s your own game world to do with as you please! Keep it to yourself or re-distribute it under another license of your choice - or sell it and become filthy rich for all we care.

Q: I have modified the Evennia library itself, what does the license say about that?

A: Our license allows you to do whatever you want with your modified Evennia, including re-distributing or selling it, as long as you include our license and copyright info found in LICENSE.txt along with your distribution.

. . . Of course, if you fix bugs or add some new snazzy feature we softly nudge you to make those changes available so they can be added to the core Evennia package for everyone’s benefit. The license doesn’t require you to do it, but that doesn’t mean we won’t still greatly appreciate it if you do!

10.4. Licensing 107 Evennia Documentation, Release 0.9

Q: Can I re-distribute the Evennia server package along with my custom game implementation? A: Sure. As long as the text in LICENSE.txt is included. Q: What about Contributions?

The contributions in evennia/evennia/contrib are considered to be released under the same license as Evennia itself, unless the individual contributor has specifically defined otherwise.

108 Chapter 10. Developer Basics CHAPTER 11

Server Components

This chapter details the inner workings of Evennia. It is useful both for game developers and for developers working on Evennia itself.

11.1 Directory Overview

This is an overview of the directories relevant to Evennia coding.

11.1.1 The Game directory

The game directory is created with evennia --init . In the Evennia documentation we always assume it’s called mygame. Apart from the server/ subfolder within, you could reorganize this folder if you preferred a different code structure for your game. • mygame/ • commands/ - Overload default Commands or add your own Commands/Command sets here. • server/ - The structure of this folder should not change since Evennia expects it. – `conf/```_ - All server configuration files sits here. The most important file is ``settings.py. – logs/ - Portal log files are stored here (Server is logging to the terminal by default) • typeclasses/ - this folder contains empty templates for overloading default game entities of Evennia. Even- nia will automatically use the changes in those templates for the game entities it creates. • web/ - This holds the Web features of your game. • world/ - this is a “miscellaneous” folder holding everything related to the world you are building, such as build scripts and rules modules that don’t fit with one of the other folders.

109 Evennia Documentation, Release 0.9

11.1.2 Evennia library layout:

If you cloned the GIT repo following the instructions, you will have a folder named evennia. The top level of it contains Python package specific stuff such as a readme file, setup.py etc. It also has two subfoldersbin/ and evennia/ (again). The bin/ directory holds OS-specific binaries that will be used when installing Evennia with pip as per the Getting started instructions. The library itself is in the evennia subfolder. From your code you will access this subfolder simply by import evennia. • evennia • ‘‘‘__init__.py‘‘‘_ - The “flat API” of Evennia resides here. • ‘‘‘commands/‘‘‘_ - The command parser and handler. – default/ - The default commands and cmdsets. • ‘‘‘comms/‘‘‘_ - 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/‘‘‘_ - Handles the storage and creation of help entries. • locale/ - Language files (i18n). • ‘‘‘locks/‘‘‘_ - Lock system for restricting access to in-game entities. • ‘‘‘objects/‘‘‘_ - In-game entities (all types of items and Characters). • ‘‘‘prototypes/‘‘‘_ - Object Prototype/spawning system and OLC menu • ‘‘‘accounts/‘‘‘_ - Out-of-game Session-controlled entities (accounts, bots etc) • ‘‘‘scripts/‘‘‘_ - Out-of-game entities equivalence to Objects, also with timer support. • ‘‘‘server/‘‘‘_ - Core server code and Session handling. – portal/ - Portal proxy and connection protocols. • `settings_default.py```_ - Root settings of Evennia. Copy settings from here to ``mygame/server/settings.py file. • ‘‘‘typeclasses/‘‘‘_ - Abstract classes for the typeclass storage and database system. • ‘‘‘utils/‘‘‘_ - Various miscellaneous useful coding resources. • ‘‘‘web/‘‘‘_ - Web resources and webserver. Partly copied into game directory on initialization. All directories contain files ending in .py. These are Python modules and are the basic units of Python code. The roots of directories also have (usually empty) files named __init__.py. These are required by Python so as to be able to find and import modules in other directories. When you have run Evennia at least once you will find that there will also be .pyc files appearing, these are pre-compiled binary versions of the .py files to speed up execution. The root of the evennia folder has an __init__.py file containing the “flat API”. This holds shortcuts to various subfolders in the evennia library. It is provided to make it easier to find things; it allows you to just import evennia and access things from that rather than having to import from their actual locations inside the source tree.

11.2 Portal And Server

Evennia consists of two processes, known as Portal and Server. They can be controlled from inside the game or from the command line as described here.

110 Chapter 11. Server Components Evennia Documentation, Release 0.9

If you are new to the concept, the main purpose of separating the two is to have accounts connect to the Portal but keep the MUD running on the Server. This way one can restart/reload the game (the Server part) without Accounts getting disconnected.

The Server and Portal are glued together via an AMP (Asynchronous Messaging Protocol) connection. This allows the two programs to communicate seamlessly.

11.3 Sessions

An Evennia Session represents one single established connection to the server. Depending on the Evennia session, it is possible for a person to connect multiple times, for example using different clients in multiple windows. Each such connection is represented by a session object. A session object has its own cmdset, usually the “unloggedin” cmdset. This is what is used to show the login screen and to handle commands to create a new account (or Account in evennia lingo) read initial help and to log into the game with an existing account. A session object can either be “logged in” or not. Logged in means that the user has authenticated. When this happens the session is associated with an Account object (which is what holds account- centric stuff). The account can then in turn puppet any number of objects/characters. Warning: A Session is not persistent - it is not a Typeclass and has no connection to the database. The Session will go away when a user disconnects and you will lose any custom data on it if the server reloads. The .db handler on Sessions is there to present a uniform API (so you can assume .db exists even if you don’t know if you receive an Object or a Session), but this is just an alias to .ndb. So don’t store any data on Sessions that you can’t afford to lose in a reload. You have been warned.

11.3.1 Properties on Sessions

Here are some important properties available on (Server-)Sessions • sessid - The unique session-id. This is an integer starting from 1. • address - The connected client’s address. Different protocols give different information here. • logged_in - True if the user authenticated to this session. • account - The Account this Session is attached to. If not logged in yet, this is None. • puppet - The Character/Object currently puppeted by this Account/Session combo. If not logged in or in OOC mode, this is None. • ndb - The Non-persistent Attribute handler. • db - As noted above, Sessions don’t have regular Attributes. This is an alias to ndb. • cmdset - The Session’s CmdSetHandler Session statistics are mainly used internally by Evennia. • conn_time - How long this Session has been connected

11.3. Sessions 111 Evennia Documentation, Release 0.9

• cmd_last - Last active time stamp. This will be reset by sending idle keepalives. • cmd_last_visible - last active time stamp. This ignores idle keepalives and representes the last time this session was truly visibly active. • cmd_total - Total number of Commands passed through this Session.

11.3.2 Multisession mode

The number of sessions possible to connect to a given account at the same time and how it works is given by the MULTISESSION_MODE setting: • MULTISESSION_MODE=0: One session per account. When connecting with a new session the old one is disconnected. This is the default mode and emulates many classic mud code bases. In default Evennia, this mode also changes how the create account Command works - it will automatically create a Character with the same name as the Account. When logging in, the login command is also modified to have the player automatically puppet that Character. This makes the distinction between Account and Character minimal from the player’s perspective. • MULTISESSION_MODE=1: Many sessions per account, input/output from/to each session is treated the same. For the player this means they can connect to the game from multiple clients and see the same output in all of them. The result of a command given in one client (that is, through one Session) will be returned to all connected Sessions/clients with no distinction. This mode will have the Session(s) auto-create and puppet a Character in the same way as mode 0. • MULTISESSION_MODE=2: Many sessions per account, one character per session. In this mode, puppeting an Object/Character will link the puppet back only to the particular Session doing the puppeting. That is, input from that Session will make use of the CmdSet of that Object/Character and outgoing messages (such as the result of a look) will be passed back only to that puppeting Session. If another Session tries to puppet the same Character, the old Session will automatically un-puppet it. From the player’s perspective, this will mean that they can open separate game clients and play a different Character in each using one game account. This mode will not auto-create a Character and not auto-puppet on login like in modes 0 and 1. Instead it changes how the account-cmdsets’s OOCLook command works so as to show a simple ‘character select’ menu. • MULTISESSION_MODE=3: Many sessions per account and character. This is the full multi-puppeting mode, where multiple sessions may not only connect to the player account but multiple sessions may also puppet a single character at the same time. From the user’s perspective it means one can open multiple client windows, some for controlling different Characters and some that share a Character’s input/output like in mode 1. This mode otherwise works the same as mode 2. Note that even if multiple Sessions puppet one Character, there is only ever one instance of that Character.

11.3.3 Returning data to the session

When you use msg() to return data to a user, the object on which you call the msg() matters. The MULTISESSION_MODE also matters, especially if greater than 1. For example, if you use account.msg("hello") there is no way for evennia to know which session it should send the greeting to. In this case it will send it to all sessions. If you want a specific session you need to supply its session to the msg call (account.msg("hello", session=mysession)). On the other hand, if you call the msg() message on a puppeted object, like character.msg("hello"), the character already knows the session that controls it - it will cleverly auto-add this for you (you can specify a different session if you specifically want to send stuff to another session).

112 Chapter 11. Server Components Evennia Documentation, Release 0.9

Finally, there is a wrapper for msg() on all command classes: command.msg(). This will transparently detect which session was triggering the command (if any) and redirects to that session (this is most often what you want). If you are having trouble redirecting to a given session, command.msg() is often the safest bet. You can get the session in two main ways: • Accounts and Objects (including Characters) have a sessions property. This is a handler that tracks all Sessions attached to or puppeting them. Use e.g. accounts.sessions.get() to get a list of Sessions attached to that entity. • A Command instance has a session property that always points back to the Session that triggered it (it’s always a single one). It will be None if no session is involved, like when a mob or script triggers the Command.

11.3.4 Customizing the Session object

When would one want to customize the Session object? Consider for example a character creation system: You might decide to keep this on the out-of-character level. This would mean that you create the character at the end of some sort of menu choice. The actual char-create cmdset would then normally be put on the account. This works fine as long as you are MULTISESSION_MODE below 2. For higher modes, replacing the Account cmdset will affect all your connected sessions, also those not involved in character creation. In this case you want to instead put the char-create cmdset on the Session level - then all other sessions will keep working normally despite you creating a new character in one of them. By default, the session object gets the commands.default_cmdsets.UnloggedinCmdSet when the user first connects. Once the session is authenticated it has no default sets. To add a “logged-in” cmdset to the Session, give the path to the cmdset class with settings.CMDSET_SESSION. This set will then henceforth always be present as soon as the account logs in.

To customize further you can completely override the Session with your own subclass. To replace the default Session class, change settings.SERVER_SESSION_CLASS to point to your custom class. This is a dangerous practice and errors can easily make your game unplayable. Make sure to take heed of the original and make your changes carefully.

11.3.5 Portal and Server Sessions

Note: This is considered an advanced topic. You don’t need to know this on a first read-through. Evennia is split into two parts, the Portal and the Server. Each side tracks its own Sessions, syncing them to each other. The “Session” we normally refer to is actually the ServerSession. Its counter-part on the Portal side is the PortalSession. Whereas the server sessions deal with game states, the portal session deals with details of the connection-protocol itself. The two are also acting as backups of critical data such as when the server reboots.

New Account connections are listened for and handled by the Portal using the protocols it understands (such as telnet, ssh, webclient etc). When a new connection is established, a PortalSession is created on the Portal side. This session object looks different depending on which protocol is used to connect, but all still have a minimum set of attributes that are generic to all sessions.

11.3. Sessions 113 Evennia Documentation, Release 0.9

These common properties are piped from the Portal, through the AMP connection, to the Server, which is now in- formed a new connection has been established. On the Server side, a ServerSession object is created to represent this. There is only one type of ServerSession; It looks the same regardless of how the Account connects. From now on, there is a one-to-one match between the ServerSession on one side of the AMP connection and the PortalSession on the other. Data arriving to the Portal Session is sent on to its mirror Server session and vice versa.

During certain situations, the portal- and server-side sessions are “synced” with each other:

• The Player closes their client, killing the Portal Session. The Portal syncs with the Server to make sure the corresponding Server Session is also deleted. • The Player quits from inside the game, killing the Server Session. The Server then syncs with the Portal to make sure to close the Portal connection cleanly. • The Server is rebooted/reset/shutdown - The Server Sessions are copied over (“saved”) to the Portal side. When the Server comes back up, this data is returned by the Portal so the two are again in sync. This way an Account’s login status and other connection-critical things can survive a server reboot (assuming the Portal is not stopped at the same time, obviously).

11.3.6 Sessionhandlers

Both the Portal and Server each have a sessionhandler to manage the connections. These handlers are global entities contain all methods for relaying data across the AMP bridge. All types of Sessions hold a reference to their respective Sessionhandler (the property is called sessionhandler) so they can relay data. See protocols for more info on building new protocols.

To get all Sessions in the game (i.e. all currently connected clients), you access the server-side Session handler, which you get by from evennia.server.sessionhandler import SESSION_HANDLER

Note: The ``SESSION_HANDLER`` singleton has an older alias ``SESSIONS`` that is commonly seen in various places as well.

See the sessionhandler.py module for details on the capabilities of the ServerSessionHandler.

11.4 Messagepath

The main functionality of Evennia is to communicate with clients connected to it; a player enters commands or their client queries for a gui update (ingoing data). The server responds or sends data on its own as the game changes (outgoing data). It’s important to understand how this flow of information works in Evennia.

11.4.1 The ingoing message path

We’ll start by tracing data from the client to the server. Here it is in short:

114 Chapter 11. Server Components Evennia Documentation, Release 0.9

Client-> PortalSession-> PortalSessionhandler-> (AMP)-> ServerSessionHandler-> ServerSession-> Inputfunc

Client

The client sends data to Evennia in two ways. • When first connecting, the client can send data to the server about its capabilities. This is things like “I support xterm256 but not unicode” and is mainly used when a Telnet client connects. This is called a “handshake” and will generally set some flags on the Portal Session that are later synced to the Server Session. Since this is not something the player controls, we’ll not explore this further here. • The client can send an inputcommand to the server. Traditionally this only happens when the player enters text on the command line. But with a custom client GUI, a command could also come from the pressing of a button. Finally the client may send commands based on a timer or some trigger.

Exactly how the inputcommand looks when it travels from the client to Evennia depends on the Protocol used:

• Telnet: A string. If GMCP or MSDP OOB protocols are used, this string will be formatted in a special way, but it’s still a raw string. If Telnet SSL is active, the string will be encrypted. • SSH: An encrypted string • Webclient: A JSON-serialized string.

Portal Session

Each client is connected to the game via a Portal Session, one per connection. This Session is different depending on the type of connection (telnet, webclient etc) and thus know how to handle that particular data type. So regardless of how the data arrives, the Session will identify the type of the instruction and any arguments it should have. For example, the telnet protocol will figure that anything arriving normally over the wire should be passed on as a “text” type.

PortalSessionHandler

The PortalSessionhandler manages all connected Sessions in the Portal. Its data_in method (called by each Portal Session) will parse the command names and arguments from the protocols and convert them to a standardized form we call the inputcommand:

(commandname, (args), {kwargs})

All inputcommands must have a name, but they may or may not have arguments and keyword arguments - in fact no default inputcommands use kwargs at all. The most common inputcommand is “text”, which has the argument the player input on the command line:

11.4. Messagepath 115 Evennia Documentation, Release 0.9

("text",("look",), {})

This inputcommand-structure is pickled together with the unique session-id of the Session to which it belongs. This is then sent over the AMP connection.

ServerSessionHandler

On the Server side, the AMP unpickles the data and associates the session id with the server-side Session. Data and Session are passed to the server-side SessionHandler.data_in. This in turn calls ServerSession. data_in()

ServerSession

The method ServerSession.data_in is meant to offer a single place to override if they want to examine all data passing into the server from the client. It is meant to call the ssessionhandler.call_inputfuncs with the (potentially processed) data (so this is technically a sort of detour back to the sessionhandler). In call_inputfuncs, the inputcommand’s name is compared against the names of all the inputfuncs registered with the server. The inputfuncs are named the same as the inputcommand they are supposed to handle, so the (default) inputfunc for handling our “look” command is called “text”. These are just normal functions and one can plugin new ones by simply putting them in a module where Evennia looks for such functions. If a matching inputfunc is found, it will be called with the Session and the inputcommand’s arguments:

text(session, *("look",), **{})

If no matching inputfunc is found, an inputfunc named “default” will be tried and if that is also not found, an error will be raised.

Inputfunc

The Inputfunc must be on the form func(session, *args, **kwargs). An exception is the default inputfunc which has form default(session, cmdname, *args, **kwargs), where cmdname is the un- matched inputcommand string. This is where the message’s path diverges, since just what happens next depends on the type of inputfunc was triggered. In the example of sending “look”, the inputfunc is named “text”. It will pass the argument to the cmdhandler which will eventually lead to the look command being executed.

11.4.2 The outgoing message path

Next let’s trace the passage from server to client.

msg-> ServerSession-> ServerSessionHandler-> (AMP)-> PortalSessionHandler-> PortalSession-> Client

116 Chapter 11. Server Components Evennia Documentation, Release 0.9

msg

All outgoing messages start in the msg method. This is accessible from three places: • Object.msg • Account.msg • Session.msg The call sign of the msg method looks like this:

msg(text=None, from_obj=None, session=None, options=None, **kwargs)

For our purposes, what is important to know is that with the exception of from_obj, session and options, all keywords given to the msg method is the name of an outputcommand and its arguments. So text is actually such a command, taking a string as its argument. The reason text sits as the first keyword argument is that it’s so commonly used (caller.msg("Text") for example). Here are some examples

msg("Hello!") # using the 'text' outputfunc msg(prompt="HP:%i, SP: %i, MP: %i"% (HP, SP, MP)) msg(mycommand=((1,2,3,4), {"foo":"bar"})

Note the form of the mycommand outputfunction. This explicitly defines the arguments and keyword arguments for the function. In the case of the text and prompt calls we just specify a string - this works too: The system will convert this into a single argument for us later in the message path. Note: The msg method sits on your Object- and Account typeclasses. It means you can easily override msg and make custom- or per-object modifications to the flow of data as it passes through.

Session

Nothing is processed on the Session, it just serves as a gathering points for all different msg. It immediately passes the data on to . . .

ServerSessionHandler

In the ServerSessionhandler, the keywords from the msg method are collated into one or more outputcommands on a standardized form (identical to inputcommands):

(commandname, (args), {kwargs})

This will intelligently convert different input to the same form. So msg("Hello") will end up as an outputcommand ("text", ("Hello",), {}). This is also the point where Inlinefuncs are parsed, depending on the session to receive the data. Said data is pickled together with the Session id then sent over the AMP bridge.

PortalSessionHandler

After the AMP connection has unpickled the data and paired the session id to the matching PortalSession, the handler next determines if this Session has a suitable method for handling the outputcommand. The situation is analogous to how inputfuncs work, except that protocols are fixed things that don’t need a plugin infrastructure like the inputfuncs are handled. So instead of an “outputfunc”, the handler looks for methods on the PortalSession with names of the form send_.

11.4. Messagepath 117 Evennia Documentation, Release 0.9

For example, the common sending of text expects a PortalSession method send_text. This will be called as send_text(*("Hello",), **{}). If the “prompt” outputfunction was used, send_prompt is called. In all other cases the send_default(cmdname, *args, **kwargs) will be called - this is the case for all client- custom outputcommands, like when wanting to tell the client to update a graphic or play a sound.

PortalSession

At this point it is up to the session to convert the command into a form understood by this particular protocol. For telnet, send_text will just send the argument as a string (since that is what telnet clients expect when “text” is coming). If send_default was called (basically everything that is not traditional text or a prompt), it will pack the data as an GMCP or MSDP command packet if the telnet client supports either (otherwise it won’t send at all). If sending to the webclient, the data will get packed into a JSON structure at all times.

Client

Once arrived at the client, the outputcommand is handled in the way supported by the client (or it may be quietly ignored if not). “text” commands will be displayed in the main window while others may trigger changes in the GUI or play a sound etc.

11.5 OOB

OOB, or Out-Of-Band, means sending data between Evennia and the user’s client without the user prompting it or necessarily being aware that it’s being passed. Common uses would be to update client health-bars, handle client button-presses or to display certain tagged text in a different window pane.

11.5.1 Briefly on input/outputcommands

Inside Evennia, all server-client communication happens in the same way (so plain text is also an ‘OOB message’ as far as Evennia is concerned). The message follows the Message Path. You should read up on that if you are unfamiliar with it. As the message travels along the path it has a standardized internal form: a tuple with a string, a tuple and a dict:

("cmdname", (args), {kwargs})

This is often referred to as an inputcommand or outputcommand, depending on the direction it’s traveling. The end point for an inputcommand, (the ‘Evennia-end’ of the message path) is a matching Inputfunc. This function is called as cmdname(session, *args, **kwargs) where session is the Session-source of the command. Inputfuncs can easily be added by the developer to support/map client commands to actions inside Evennia (see the inputfunc page for more details). When a message is outgoing (at the ‘Client-end’ of the message path) the outputcommand is handled by a matching Outputfunc. This is responsible for converting the internal Evennia representation to a form suitable to send over the wire to the Client. Outputfuncs are hard-coded. Which is chosen and how it processes the outgoing data depends on the nature of the client it’s connected to. The only time one would want to add new outputfuncs is as part of developing support for a new Evennia Protocol.

11.5.2 Sending and receiving an OOB message

Sending is simple. You just use the normal msg method of the object whose session you want to send to. For example in a Command:

118 Chapter 11. Server Components Evennia Documentation, Release 0.9

caller.msg(cmdname=((args,...), {key:value,...}))

A special case is the text input/outputfunc. It’s so common that it’s the default of the msg method. So these are equivalent:

caller.msg("Hello") caller.msg(text="Hello")

You don’t have to specify the full output/input definition. So for example, if your particular command only needs kwargs, you can skip the (args) part. Like in the text case you can skip writing the tuple if there is only one arg . . . and so on - the input is pretty flexible. If there are no args at all you need to give the empty tuple msg(cmdname=(,) (giving None would mean a single argument None). Which commands you can send depends on the client. If the client does not support an explicit OOB protocol (like many old/legacy MUD clients) Evennia can only send text to them and will quietly drop any other types of output- funcs. Remember that a given message may go to multiple clients with different capabilities. So unless you turn off telnet completely and only rely on the webclient, you should never rely on non-text OOB messages always reaching all targets. Inputfuncs lists the default inputfuncs available to handle incoming OOB messages. To accept more you need to add more inputfuncs (see that page for more info).

11.5.3 Supported OOB protocols

Evennia supports clients using one of the following protocols:

Telnet

By default telnet (and telnet+SSL) supports only the plain text outputcommand. Evennia however detects if the Client supports one of two MUD-specific OOB extensions to the standard telnet protocol - GMCP or MSDP. Evennia supports both simultaneously and will switch to the protocol the client uses. If the client supports both, GMCP will be used. Note that for Telnet, text has a special status as the “in-band” operation. So the text outputcommand sends the text argument directly over the wire, without going through the OOB translations described below.

Telnet + GMCP

GMCP, the Generic Mud Communication Protocol sends data on the form cmdname + JSONdata. Here the cmdname is expected to be on the form “Package.Subpackage”. There could also be additional Sub-sub packages etc. The names of these ‘packages’ and ‘subpackages’ are not that well standardized beyond what individual MUDs or companies have chosen to go with over the years. You can decide on your own package names, but here are what others are using: • Aardwolf GMCP • Discworld GMCP • GMCP • IRE games GMCP

11.5. OOB 119 Evennia Documentation, Release 0.9

Evennia will translate underscores to . and capitalize to fit the specification. So the outputcommand foo_bar will become a GMCP command-name Foo.Bar. A GMCP command “Foo.Bar” will be come foo_bar. To send a GMCP command that turns into an Evennia inputcommand without an underscore, use the Core package. So Core. Cmdname becomes just cmdname in Evennia and vice versa. On the wire, a GMCP instruction for ("cmdname", ("arg",), {}) will look like this:

IAC SB GMCP"cmdname""arg" IAC SE

where all the capitalized words are telnet character constants specified in evennia/server/portal/ telnet_oob.py. These are parsed/added by the protocol and we don’t include these in the listings below.

Input/Outputfunc GMCP-Command [cmd_name, [], {}] Cmd.Name [cmd_name, [arg], {}] Cmd.Name arg [cmd_na_me, [args],{}] Cmd.Na.Me [args] [cmd_name, [], {kwargs}] Cmd.Name {kwargs} [cmdname, [args, {kwargs}] Core.Cmdname [[args],{kwargs}]

Since Evennia already supplies default inputfuncs that don’t match the names expected by the most common GMCP implementations we have a few hard-coded mappings for those:

GMCP command name Input/Outputfunc name “Core.Hello” “client_options” “Core.Supports.Get” “client_options” “Core.Commands.Get” “get_inputfuncs” “Char.Value.Get” “get_value” “Char.Repeat.Update” “repeat” “Char.Monitor.Update” “monitor”

Telnet + MSDP

MSDP, the Mud Server Data Protocol, is a competing standard to GMCP. The MSDP protocol page specifies a range of “recommended” available MSDP command names. Evennia does not support those - since MSDP doesn’t specify a special format for its command names (like GMCP does) the client can and should just call the internal Evennia inputfunc by its actual name. MSDP uses Telnet character constants to package various structured data over the wire. MSDP supports strings, arrays (lists) and tables (dicts). These are used to define the cmdname, args and kwargs needed. When sending MSDP for ("cmdname", ("arg",), {}) the resulting MSDP instruction will look like this:

IAC SB MSDP VAR cmdname VAL arg IAC SE

The various available MSDP constants like VAR (variable), VAL (value), ARRAYOPEN/ARRAYCLOSE and TABLEOPEN/TABLECLOSE are specified in evennia/server/portal/telnet_oob.

120 Chapter 11. Server Components Evennia Documentation, Release 0.9

Outputfunc/Inputfunc MSDP instruction [cmdname, [], VAR cmdname VAL {}] VAR cmdname VAL arg ‘‘[cmdname, [arg], {}] ‘‘ VAR cmdname VAL ARRAYOPEN VAL arg VAL arg . . . ARRAYCLOSE ‘‘[cmdname, [args],{}] ‘‘ [cmdname, [], VAR cmdname VAL TABLEOPEN VAR key VAL val . . . TABLECLOSE {kwarg s}] [cmdname, VAR cmdname VAL ARRAYOPEN VAL arg VAL arg . . . ARRAYCLOSE VAR cmd- [args], { name VAL TABLEOPEN VAR key VAL val . . . TABLECLOSE wargs}]

Observe that VAR ... VAL always identifies cmdnames, so if there are multiple arrays/dicts tagged with the same cmdname they will be appended to the args, kwargs of that inputfunc. Vice-versa, a different VAR ... VAL (outside a table) will come out as a second, different command input.

SSH

SSH only supports the text input/outputcommand.

Web client

Our web client uses pure JSON structures for all its communication, including text. This maps directly to the Evennia internal output/inputcommand, including eventual empty args/kwargs. So the same example ("cmdname", ("arg",), {}) will be sent/received as a valid JSON structure

["cmdname, ["arg"], {}]

Since JSON is native to Javascript, this becomes very easy for the webclient to handle.

11.6 Inputfuncs

An inputfunc is an Evennia function that handles a particular input (an inputcommand) from the client. The inputfunc is the last destination for the inputcommand along the ingoing message path. The inputcommand always has the form (commandname, (args), {kwargs}) and Evennia will use this to try to find and call an inputfunc on the form

def commandname(session, *args, **kwargs): # ...

Or, if no match was found, it will call an inputfunc named “default” on this form

def default(session, cmdname, *args, **kwargs): # cmdname is the name of the mismatched inputcommand

11.6. Inputfuncs 121 Evennia Documentation, Release 0.9

11.6.1 Adding your own inputfuncs

This is simple. Add a function on the above form to mygame/server/conf/inputfuncs.py. Your function must be in the global, outermost scope of that module and not start with an underscore (_) to be recognized as an inputfunc. Reload the server. That’s it. To overload a default inputfunc (see below), just add a function with the same name. The modules Evennia looks into for inputfuncs are defined in the list settings.INPUT_FUNC_MODULES. This list will be imported from left to right and later imported functions will replace earlier ones.

11.6.2 Default inputfuncs

Evennia defines a few default inputfuncs to handle the common cases. These are defined in evennia/server/ inputfuncs.py. text

• Input: ("text", (textstring,), {}) • Output: Depends on Command triggered This is the most common of inputcommands, and the only one supported by every traditional mud. The argument is usually what the user sent from their command line. Since all text input from the user like this is considered a Com- mand, this inputfunc will do things like nick-replacement and then pass on the input to the central Commandhandler. echo

• Input: ("echo", (args), {}) • Output: ("text", ("Echo returns: %s" % args), {}) This is a test input, which just echoes the argument back to the session as text. Can be used for testing custom client input. default

The default function, as mentioned above, absorbs all non-recognized inputcommands. The default one will just log an error. client_options

• Input: ("client_options, (), {key:value, ...}) • Output: • normal: None • get: ("client_options", (), {key:value, ...}) This is a direct command for setting protocol options. These are settable with the @option command, but this offers a client-side way to set them. Not all connection protocols makes use of all flags, but here are the possible keywords: • get (bool): If this is true, ignore all other kwargs and immediately return the current settings as an outputcom- mand ("client_options", (), {key=value, ...})- • client (str): A client identifier, like “mushclient”.

122 Chapter 11. Server Components Evennia Documentation, Release 0.9

• version (str): A client version • ansi (bool): Supports ansi colors • xterm256 (bool): Supports xterm256 colors or not • mxp (bool): Supports MXP or not • utf-8 (bool): Supports UTF-8 or not • screenreader (bool): Screen-reader mode on/off • mccp (bool): MCCP compression on/off • screenheight (int): Screen height in lines • screenwidth (int): Screen width in characters • inputdebug (bool): Debug input functions • nomarkup (bool): Strip all text tags • raw (bool): Leave text tags unparsed Note that there are two GMCP aliases to this inputfunc - hello and supports_set, which means it will be accessed via the GMCP Hello and Supports.Set instructions assumed by some clients. get_client_options

• Input: ("get_client_options, (), {key:value, ...}) • Output: ("client_options, (), {key:value, ...}) This is a convenience wrapper that retrieves the current options by sending “get” to client_options above. get_inputfuncs

• Input: ("get_inputfuncs", (), {}) • Output: ("get_inputfuncs", (), {funcname:docstring, ...}) Returns an outputcommand on the form ("get_inputfuncs", (), {funcname:docstring, ...}) - a list of all the available inputfunctions along with their docstrings. login

Note: this is currently experimental and not very well tested. • Input: ("login", (username, password), {}) • Output: Depends on login hooks This performs the inputfunc version of a login operation on the current Session. get_value

Input: ("get_value", (name, ), {}) Output: ("get_value", (value, ), {})

11.6. Inputfuncs 123 Evennia Documentation, Release 0.9

Retrieves a value from the Character or Account currently controlled by this Session. Takes one argument, This will only accept particular white-listed names, you’ll need to overload the function to expand. By default the following values can be retrieved: • “name” or “key”: The key of the Account or puppeted Character. • “location”: Name of the current location, or “None”. • “servername”: Name of the Evennia server connected to. repeat

• Input: ("repeat", (), {"callback":funcname, "interval": secs, "stop": False}) • Output: Depends on the repeated function. Will return ("text", (repeatlist),{} with a list of ac- cepted names if given an unfamiliar callback name. This will tell evennia to repeatedly call a named function at a given interval. Behind the scenes this will set up a Ticker. Only previously acceptable functions are possible to repeat-call in this way, you’ll need to overload this inputfunc to add the ones you want to offer. By default only two example functions are allowed, “test1” and “test2”, which will just echo a text back at the given interval. Stop the repeat by sending "stop": True (note that you must include both the callback name and interval for Evennia to know what to stop). unrepeat

• Input: ("unrepeat", (), ("callback":funcname, "interval": secs) • Output: None This is a convenience wrapper for sending “stop” to the repeat inputfunc. monitor

• Input: ("monitor", (), ("name":field_or_argname, stop=False) • Output (on change): ("monitor", (), {"name":name, "value":value}) This sets up on-object monitoring of Attributes or database fields. Whenever the field or Attribute changes in any way, the outputcommand will be sent. This is using the MonitorHandler behind the scenes. Pass the “stop” key to stop monitoring. Note that you must supply the name also when stopping to let the system know which monitor should be cancelled. Only fields/attributes in a whitelist are allowed to be used, you have to overload this function to add more. By default the following fields/attributes can be monitored: • “name”: The current character name • “location”: The current location • “desc”: The description Argument

11.6.3 unmonitor

• Input: ("unmonitor", (), {"name":name}) • Output: None A convenience wrapper that sends “stop” to the monitor function.

124 Chapter 11. Server Components Evennia Documentation, Release 0.9

11.7 Custom Protocols

Note: This is considered an advanced topic and is mostly of interest to users planning to implement their own custom client protocol.

A PortalSession is the basic data object representing an external connection to the Evennia Portal – usually a human player running a mud client of some kind. The way they connect (the language the player’s client and Evennia use to talk to each other) is called the connection Protocol. The most common such protocol for MUD:s is the Telnet protocol. All Portal Sessions are stored and managed by the Portal’s sessionhandler.

It’s technically sometimes hard to separate the concept of PortalSession from the concept of Protocol since both depend heavily on the other (they are often created as the same class). When data flows through this part of the system, this is how it goes

# In the Portal You<-> Protocol+ PortalSession<-> PortalSessionHandler<-> (AMP)<-> ServerSessionHandler<-> ServerSession<-> InputFunc

(See the Message Path for the bigger picture of how data flows through Evennia). The parts that needs to be customized to make your own custom protocol is the Protocol + PortalSession (which translates between data coming in/out over the wire to/from Evennia internal representation) as well as the InputFunc (which handles incoming data).

11.7.1 Adding custom Protocols

Evennia has a plugin-system that add the protocol as a new “service” to the application.

Take a look at evennia/server/portal/portal.py, notably the sections towards the end of that file. These are where the various in-built services like telnet, ssh, webclient etc are added to the Portal (there is an equivalent but shorter list in evennia/server/server.py).

To add a new service of your own (for example your own custom client protocol) to the Portal or Server, look at mygame/server/conf/server_services_plugins and portal_services_plugins. By default Evennia will look into these modules to find plugins. If you wanted to have it look for more modules, you could do the following:

11.7. Custom Protocols 125 Evennia Documentation, Release 0.9

# add to the Server SERVER_SERVICES_PLUGIN_MODULES.append('server.conf.my_server_plugins') # or, if you want to add to the Portal PORTAL_SERVICES_PLUGIN_MODULES.append('server.conf.my_portal_plugins')

When adding a new connection you’ll most likely only need to add new things to the PORTAL_SERVICES_PLUGIN_MODULES.

This module can contain whatever you need to define your protocol, but it must contain a function start_plugin_services(app). This is called by the Portal as part of its upstart. The function start_plugin_services must contain all startup code the server need. The app argument is a reference to the Portal/Server application itself so the custom service can be added to it. The function should not return anything.

This is how it looks:

# mygame/server/conf/portal_services_plugins.py

# here the new Portal Twisted protocol is defined class MyOwnFactory(...): [...]

# some configs MYPROC_ENABLED= True # convenient off-flag to avoid having to edit settings all the

˓→time MY_PORT= 6666

def start_plugin_services(portal): "This is called by the Portal during startup" if not MYPROC_ENABLED: return # output to list this with the other services at startup print(" myproc: %s"% MY_PORT)

# some setup (simple example) factory= MyOwnFactory() my_service= internet.TCPServer(MY_PORT, factory) # all Evennia services must be uniquely named my_service.setName("MyService") # add to the main portal application portal.services.addService(my_service)

Once the module is defined and targeted in settings, just reload the server and your new protocol/services should start with the others.

11.7.2 Writing your own Protocol

Writing a stable communication protocol from scratch is not something we’ll cover here, it’s no trivial task. The good news is that Twisted offers implementations of many common protocols, ready for adapting.

126 Chapter 11. Server Components Evennia Documentation, Release 0.9

Writing a protocol implementation in Twisted usually involves creating a class inheriting from an already existing Twisted protocol class and from evennia.server.session.Session (multiple inheritance), then overloading the methods that particular protocol uses to link them to the Evennia-specific inputs. Here’s a example to show the concept:

# In module that we'll later add to the system through PORTAL_SERVICE_PLUGIN_MODULES

# pseudo code from twisted.something import TwistedClient # this class is used both for Portal- and Server Sessions from evennia.server.session import Session from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS class MyCustomClient(TwistedClient, Session):

def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.sessionhandler= PORTAL_SESSIONS

# these are methods we must know that TwistedClient uses for # communication. Name and arguments could vary for different Twisted protocols def onOpen(self, *args, **kwargs): # let's say this is called when the client first connects

# we need to init the session and connect to the sessionhandler. The .factory # is available through the Twisted parents

client_address= self.getClientAddress() # get client address somehow

self.init_session("mycustom_protocol", client_address, self.factory.

˓→sessionhandler) self.sessionhandler.connect(self)

def onClose(self, reason, *args, **kwargs): # called when the client connection is dropped # link to the Evennia equivalent self.disconnect(reason)

def onMessage(self, indata, *args, **kwargs): # called with incoming data # convert as needed here self.data_in(data=indata)

def sendMessage(self, outdata, *args, **kwargs): # called to send data out # modify if needed super().sendMessage(self, outdata, *args, **kwargs)

# these are Evennia methods. They must all exist and look exactly like this # The above twisted-methods call them and vice-versa. This connects the protocol # the Evennia internals.

def disconnect(self, reason=None): """ Called when connection closes. This can also be called directly by Evennia when manually closing the

˓→connection. (continues on next page)

11.7. Custom Protocols 127 Evennia Documentation, Release 0.9

(continued from previous page) Do any cleanups here. """ self.sessionhandler.disconnect(self)

def at_login(self): """ Called when this session authenticates by the server (if applicable) """

def data_in(self, **kwargs): """ Data going into the server should go through this method. It should pass data into `sessionhandler.data_in`. THis will be called by the sessionhandler with the data it gets from the approrpriate send_* method found later in this protocol. """ self.sessionhandler.data_in(self, text=kwargs['data'])

def data_out(self, **kwargs): """ Data going out from the server should go through this method. It should hand off to the protocol's send method, whatever it's called. """ # we assume we have a 'text' outputfunc self.onMessage(kwargs['text'])

# 'outputfuncs' are defined as `send_`. From in-code, they are

˓→called # with `msg(outfunc_name=)`.

def send_text(self, txt, *args, **kwargs): """ Send text, used with e.g. `session.msg(text="foo")` """ # we make use of the self.data_out(text=txt)

def send_default(self, cmdname, *args, **kwargs): """ Handles all outputfuncs without an explicit `send_*` method to handle them. """ self.data_out(**{cmdname: str(args)})

The principle here is that the Twisted-specific methods are overridden to redirect inputs/outputs to the Evennia-specific methods.

Sending data out

To send data out through this protocol, you’d need to get its Session and then you could e.g. session.msg(text="foo")

The message will pass through the system such that the sessionhandler will dig out the session and check if it has a send_text method (it has). It will then pass the “foo” into that method, which in our case means sending “foo” across the network.

128 Chapter 11. Server Components Evennia Documentation, Release 0.9

Receiving data

Just because the protocol is there, does not mean Evennia knows what to do with it. An Inputfunc must exist to receive it. In the case of the text input exemplified above, Evennia alredy handles this input - it will parse it as a Command name followed by its inputs. So handle that you need to simply add a cmdset with commands on your receiving Session (and/or the Object/Character it is puppeting). If not you may need to add your own Inputfunc (see the Inputfunc page for how to do this.

These might not be as clear-cut in all protocols, but the principle is there. These four basic components - however they are accessed - links to the Portal Session, which is the actual common interface between the different low-level protocols and Evennia.

11.7.3 Assorted notes

To take two examples, Evennia supports the telnet protocol as well as webclient, via ajax or websockets. You’ll find that whereas telnet is a textbook example of a Twisted protocol as seen above, the ajax protocol looks quite different due to how it interacts with the webserver through long-polling (comet) style requests. All the necessary parts mentioned above are still there, but by necessity implemented in very different ways.

11.8 Commands

Commands are intimately linked to Command Sets and you need to read that page too to be familiar with how the command system works. The two pages were split for easy reading. The basic way for users to communicate with the game is through Commands. These can be commands directly related to the game world such as look, get, drop and so on, or administrative commands such as examine or @dig. The default commands coming with Evennia are ‘MUX-like’ in that they use @ for admin commands, support things like switches, syntax with the ‘=’ symbol etc, but there is nothing that prevents you from implementing a completely different command scheme for your game. You can find the default commands in evennia/commands/default. You should not edit these directly - they will be updated by the Evennia team as new features are added. Rather you should look to them for inspiration and inherit your own designs from them. There are two components to having a command running - the Command class and the Command Set (command sets were split into a separate wiki page for ease of reading). 1.A Command is a python class containing all the functioning code for what a command does - for example, a get command would contain code for picking up objects. 2.A Command Set (often referred to as a CmdSet or cmdset) is like a container for one or more Commands. A given Command can go into any number of different command sets. Only by putting the command set on a character object you will make all the commands therein available to use by that character. You can also store command sets on normal objects if you want users to be able to use the object in various ways. Consider a “Tree” object with a cmdset defining the commands climb and chop down. Or a “Clock” with a cmdset containing the single command check time. This page goes into full detail about how to use Commands. To fully use them you must also read the page detailing Command Sets. There is also a step-by-step Adding Command Tutorial that will get you started quickly without the extra explanations.

11.8. Commands 129 Evennia Documentation, Release 0.9

11.8.1 Defining Commands

All commands are implemented as normal Python classes inheriting from the base class Command (evennia. Command). You will find that this base class is very “bare”. The default commands of Evennia actually inherit from a child of Command called MuxCommand - this is the class that knows all the mux-like syntax like /switches, splitting by “=” etc. Below we’ll avoid mux-specifics and use the base Command class directly.

# basic Command definition from evennia import Command

class MyCmd(Command): """ This is the help-text for the command """ key="mycommand" def parse(self): # parsing the command line here def func(self): # executing the command here

Here is a minimalistic command with no custom parsing:

from evennia import Command

class CmdEcho(Command): key="echo"

def func(self): # echo the caller's input back to the caller self.caller.msg("Echo: {}".format(self.args)

You define a new command by assigning a few class-global properties on your inherited class and overloading one or two hook functions. The full gritty mechanic behind how commands work are found towards the end of this page; for now you only need to know that the command handler creates an instance of this class and uses that instance whenever you use this command - it also dynamically assigns the new command instance a few useful properties that you can assume to always be available.

Who is calling the command?

In Evennia there are three types of objects that may call the command. It is important to be aware of this since this will also assign appropriate caller, session, sessid and account properties on the command body at runtime. Most often the calling type is Session. •A Session. This is by far the most common case when a user is entering a command in their client. – caller - this is set to the puppeted Object if such an object exists. If no puppet is found, caller is set equal to account. Only if an Account is not found either (such as before being logged in) will this be set to the Session object itself. – session - a reference to the Session object itself. – sessid - sessid.id, a unique integer identifier of the session. – account - the Account object connected to this Session. None if not logged in. • An Account. This only happens if account.execute_cmd() was used. No Session information can be obtained in this case.

130 Chapter 11. Server Components Evennia Documentation, Release 0.9

– caller - this is set to the puppeted Object if such an object can be determined (without Session info this can only be determined in MULTISESSION_MODE=0 or 1). If no puppet is found, this is equal to account. – session - None* – sessid - None* – account - Set to the Account object. • An Object. This only happens if object.execute_cmd() was used (for example by an NPC). – caller - This is set to the calling Object in question. – session - None* – sessid - None* – account - None *): There is a way to make the Session available also inside tests run directly on Accounts and Objects, and that is to pass it to execute_cmd like so: account.execute_cmd("...", session=). Doing so will make the .session and .sessid properties available in the command.

Properties assigned to the command instance at run-time

Let’s say account Bob with a character BigGuy enters the command look at sword. After the system having successfully identified this as the “look” command and determined that BigGuy really has access to a command named look, it chugs the look command class out of storage and either loads an existing Command instance from cache or creates one. After some more checks it then assigns it the following properties: • caller - The character BigGuy, in this example. This is a reference to the object executing the command. The value of this depends on what type of object is calling the command; see the previous section. • session - the Session Bob uses to connect to the game and control BigGuy (see also previous section). • sessid - the unique id of self.session, for quick lookup. • account - the Account Bob (see previous section). • cmdstring - the matched key for the command. This would be look in our example. • args - this is the rest of the string, except the command name. So if the string entered was look at sword, args would be ” at sword“. Note the space kept - Evennia would correctly interpret lookat sword too. This is useful for things like /switches that should not use space. In the MuxCommand class used for default commands, this space is stripped. Also see the arg_regex property if you want to enforce a space to make lookat sword give a command-not-found error. • obj - the game Object on which this command is defined. This need not be the caller, but since look is a common (default) command, this is probably defined directly on BigGuy - so obj will point to BigGuy. Otherwise obj could be an Account or any interactive object with commands defined on it, like in the example of the “check time” command defined on a “Clock” object. • cmdset - this is a reference to the merged CmdSet (see below) from which this command was matched. This variable is rarely used, it’s main use is for the auto-help system( Advanced note: the merged cmdset need NOT be the same as ‘‘BigGuy.cmdset‘‘. The merged set can be a combination of the cmdsets from other objects in the room, for example). • raw_string - this is the raw input coming from the user, without stripping any surrounding whitespace. The only thing that is stripped is the ending newline marker.

11.8. Commands 131 Evennia Documentation, Release 0.9

Other useful utility methods:

• .get_help(caller, cmdset) - Get the help entry for this command. By default the arguments are not used, but they could be used to implement alternate help-display systems. • .client_width() - Shortcut for getting the client’s screen-width. Note that not all clients will truthfully report this value - that case the settings.DEFAULT_SCREEN_WIDTH will be returned. • .styled_table(*args, **kwargs) - This returns an EvTable styled based on the session calling this command. The args/kwargs are the same as for EvTable, except styling defaults are set. • .styled_header, _footer, separator - These will produce styled decorations for display to the user. They are useful for creating listings and forms with colors adjustable per-user.

Defining your own command classes

Beyond the properties Evennia always assigns to the command at run-time (listed above), your job is to define the following class properties: • key (string) - the identifier for the command, like look. This should (ideally) be unique. A key can consist of more than one word, like “press button” or “pull left lever”. Note that both key and aliases below determine the identity of a command. So two commands are considered if either matches. This is important for merging cmdsets described below. • aliases (optional list) - a list of alternate names for the command (["glance", "see", "l"]). Same name rules as for key applies. • locks (string) - a lock definition, usually on the form cmd:. Locks is a rather big topic, so until you learn more about locks, stick to giving the lockstring "cmd:all()" to make the command available to everyone (if you don’t provide a lock string, this will be assigned for you). • help_category (optional string) - setting this helps to structure the auto-help into categories. If none is set, this will be set to General. • save_for_next (optional boolean). This defaults to False. If True, a copy of this command object (along with any changes you have done to it) will be stored by the system and can be accessed by the next command by retrieving self.caller.ndb.last_cmd. The next run command will either clear or replace the storage. • arg_regex (optional raw string): Used to force the parser to limit itself and tell it when the command-name ends and arguments begin (such as requiring this to be a space or a /switch). This is done with a regular expression. See the arg_regex section for the details. • auto_help (optional boolean). Defaults to True. This allows for turning off the auto-help system on a per- command basis. This could be useful if you either want to write your help entries manually or hide the existence of a command from help’s generated list. • is_exit (bool) - this marks the command as being used for an in-game exit. This is, by default, set by all Exit objects and you should not need to set it manually unless you make your own Exit system. It is used for optimization and allows the cmdhandler to easily disregard this command when the cmdset has its no_exits flag set. • is_channel (bool)- this marks the command as being used for an in-game channel. This is, by default, set by all Channel objects and you should not need to set it manually unless you make your own Channel system. is used for optimization and allows the cmdhandler to easily disregard this command when its cmdset has its no_channels flag set. • msg_all_sessions (bool): This affects the behavior of the Command.msg method. If unset (default), call- ing self.msg(text) from the Command will always only send text to the Session that actually triggered this

132 Chapter 11. Server Components Evennia Documentation, Release 0.9

Command. If set however, self.msg(text) will send to all Sessions relevant to the object this Command sits on. Just which Sessions receives the text depends on the object and the server’s MULTISESSION_MODE. You should also implement at least two methods, parse() and func() (You could also implement perm(), but that’s not needed unless you want to fundamentally change how access checks work). • at_pre_cmd() is called very first on the command. If this function returns anything that evaluates to True the command execution is aborted at this point. • parse() is intended to parse the arguments (self.args) of the function. You can do this in any way you like, then store the result(s) in variable(s) on the command object itself (i.e. on self). To take an example, the default mux-like system uses this method to detect “command switches” and store them as a list in self. switches. Since the parsing is usually quite similar inside a command scheme you should make parse() as generic as possible and then inherit from it rather than re-implementing it over and over. In this way, the default MuxCommand class implements a parse() for all child commands to use. • func() is called right after parse() and should make use of the pre-parsed input to actually do whatever the command is supposed to do. This is the main body of the command. The return value from this method will be returned from the execution as a Twisted Deferred. • at_post_cmd() is called after func() to handle eventual cleanup. Finally, you should always make an informative doc string( __doc__) at the top of your class. This string is dynam- ically read by the Help System to create the help entry for this command. You should decide on a way to format your help and stick to that. Below is how you define a simple alternative “smile” command: from evennia import Command class CmdSmile(Command): """ A smile command

Usage: smile [at] [] grin [at] []

Smiles to someone in your vicinity or to the room in general.

(This initial string (the __doc__ string) is also used to auto-generate the help for this command) """

key="smile" aliases=["smile at","grin","grin at"] locks="cmd:all()" help_category="General"

def parse(self): "Very trivial parser" target= self.args.strip()

def func(self): "This actually does things" caller= self.caller

(continues on next page)

11.8. Commands 133 Evennia Documentation, Release 0.9

(continued from previous page) if not self.target or self.target =="here": string=" {caller.key} smiles" else: target= caller.search(self.target) if not target: return string=f" {caller.key} smiles at {target.key}"

caller.location.msg_contents(string)

The power of having commands as classes and to separate parse() and func() lies in the ability to inherit functionality without having to parse every command individually. For example, as mentioned the default commands all inherit from MuxCommand. MuxCommand implements its own version of parse() that understands all the specifics of MUX-like commands. Almost none of the default commands thus need to implement parse() at all, but can assume the incoming string is already split up and parsed in suitable ways by its parent.

Before you can actually use the command in your game, you must now store it within a command set. See the Command Sets page.

On arg_regex

The command parser is very general and does not require a space to end your command name. This means that the alias : to emote can be used like :smiles without modification. It also means getstone will get you the stone (unless there is a command specifically named getstone, then that will be used). If you want to tell the parser to require a certain separator between the command name and its arguments (so that get stone works but getstone gives you a ‘command not found’ error) you can do so with the arg_regex property. The arg_regex is a raw regular expression string. The regex will be compiled by the system at runtime. This allows you to customize how the part immediately following the command name (or alias) must look in order for the parser to match for this command. Some examples: • commandname argument (arg_regex = r"\s.+"): This forces the parser to require the command name to be followed by one or more spaces. Whatever is entered after the space will be treated as an ar- gument. However, if you’d forget the space (like a command having no arguments), this would not match commandname. • commandname or commandname argument (arg_regex = r"\s.+|$"): This makes both look and look me work but lookme will not. • commandname/switches arguments (arg_regex = r"(?:^(?:\s+|\/).*$)|^$". If you are using Evennia’s MuxCommand Command parent, you may wish to use this since it will allow /switches to work as well as having or not having a space. The arg_regex allows you to customize the behavior of your commands. You can put it in the parent class of your command to customize all children of your Commands. However, you can also change the base default behavior for all Commands by modifying settings.COMMAND_DEFAULT_ARG_REGEX.

134 Chapter 11. Server Components Evennia Documentation, Release 0.9

11.8.2 Exiting a command

Normally you just use return in one of your Command class’ hook methods to exit that method. That will however still fire the other hook methods of the Command in sequence. That’s usually what you want but sometimes it may be useful to just abort the command, for example if you find some unacceptable input in your parse method. To exit the command this way you can raise evennia.InterruptCommand: from evennia import InterruptCommand class MyCommand(Command):

# ...

def parse(self): # ... # if this fires, `func()` and `at_post_cmd` will not # be called at all raise InterruptCommand()

11.8.3 Pauses in commands

Sometimes you want to pause the execution of your command for a little while before continuing - maybe you want to simulate a heavy swing taking some time to finish, maybe you want the echo of your voice to return to you with an ever-longer delay. Since Evennia is running asynchronously, you cannot use time.sleep() in your commands (or anywhere, really). If you do, the entire game will be frozen for everyone! So don’t do that. Fortunately, Evennia offers a really quick syntax for making pauses in commands. In your func() method, you can use the yield keyword. This is a Python keyword that will freeze the current execution of your command and wait for more before processing. Note that you cannot just drop yield into any code and expect it to pause. Evennia will only pause for you if you yield inside the Command’s func() method. Don’t expect it to work anywhere else. Here’s an example of a command using a small pause of five seconds between messages: from evennia import Command class CmdWait(Command): """ A dummy command to show how to wait

Usage: wait

"""

key="wait" locks="cmd:all()" help_category="General"

def func(self): """Command execution.""" self.msg("Starting to wait ...") yield 5 self.msg("... This shows after 5 seconds. Waiting ...") yield 2 self.msg("... And now another 2 seconds have passed.")

11.8. Commands 135 Evennia Documentation, Release 0.9

The important line is the yield 5 and yield 2 lines. It will tell Evennia to pause execution here and not continue until the number of seconds given has passed. There are two things to remember when using yield in your Command’s func method: 1. The paused state produced by the yield is not saved anywhere. So if the server reloads in the middle of your command pausing, it will not resume when the server comes back up - the remainder of the command will never fire. So be careful that you are not freezing the character or account in a way that will not be cleared on reload. 2. If you use yield you may not also use return in your func method. You’ll get an error explaining this. This is due to how Python generators work. You can however use a “naked” return just fine. Usually there is no need for func to return a value, but if you ever do need to mix yield with a final return value in the same func, look at twisted.internet.defer.returnValue.

11.8.4 Asking for user input

The yield keyword can also be used to ask for user input. Again you can’t use Python’s input in your command, for it would freeze Evennia for everyone while waiting for that user to input their text. Inside a Command’s func method, the following syntax can also be used:

answer= yield("Your question")

Here’s a very simple example: class CmdConfirm(Command):

""" A dummy command to show confirmation.

Usage: confirm

"""

key="confirm"

def func(self): answer= yield("Are you sure you want to go on?") if answer.strip().lower() in ("yes",""): self.msg("Yes!") else: self.msg("No!")

This time, when the user enters the ‘confirm’ command, she will be asked if she wants to go on. Entering ‘yes’ or “y” (regardless of case) will give the first reply, otherwise the second reply will show. Note again that the yield keyword does not store state. If the game reloads while waiting for the user to answer, the user will have to start over. It is not a good idea to use yield for important or complex choices, a persistent EvMenu might be more appropriate in this case.

11.8.5 System commands

Note: This is an advanced topic. Skip it if this is your first time learning about commands.

136 Chapter 11. Server Components Evennia Documentation, Release 0.9

There are several command-situations that are exceptional in the eyes of the server. What happens if the account enters an empty string? What if the ‘command’ given is infact the name of a channel the user wants to send a message to? Or if there are multiple command possibilities? Such ‘special cases’ are handled by what’s called system commands. A system command is defined in the same way as other commands, except that their name (key) must be set to one reserved by the engine (the names are defined at the top of evennia/commands/cmdhandler.py). You can find (unused) implementations of the system commands in evennia/commands/default/system_commands.py. Since these are not (by default) included in any CmdSet they are not actually used, they are just there for show. When the special situation occurs, Evennia will look through all valid CmdSets for your custom system command. Only after that will it resort to its own, hard-coded implementation. Here are the exceptional situations that triggers system commands. You can find the command keys they use as properties on evennia.syscmdkeys: • No input (syscmdkeys.CMD_NOINPUT) - the account just pressed return without any input. Default is to do nothing, but it can be useful to do something here for certain implementations such as line editors that interpret non-commands as text input (an empty line in the editing buffer). • Command not found (syscmdkeys.CMD_NOMATCH) - No matching command was found. Default is to display the “Huh?” error message. • Several matching commands where found (syscmdkeys.CMD_MULTIMATCH) - Default is to show a list of matches. • User is not allowed to execute the command (syscmdkeys.CMD_NOPERM) - Default is to display the “Huh?” error message. • Channel (syscmdkeys.CMD_CHANNEL) - This is a Channel name of a channel you are subscribing to - Default is to relay the command’s argument to that channel. Such commands are created by the Comm system on the fly depending on your subscriptions. • New session connection (syscmdkeys.CMD_LOGINSTART). This command name should be put in the settings.CMDSET_UNLOGGEDIN. Whenever a new connection is established, this command is always called on the server (default is to show the login screen). Below is an example of redefining what happens when the account doesn’t provide any input (e.g. just presses return). Of course the new system command must be added to a cmdset as well before it will work. from evennia import syscmdkeys, Command class MyNoInputCommand(Command): "Usage: Just press return, I dare you" key= syscmdkeys.CMD_NOINPUT def func(self): self.caller.msg("Don't just press return like that, talk to me!")

11.8.6 Dynamic Commands

Note: This is an advanced topic. Normally Commands are created as fixed classes and used without modification. There are however situations when the exact key, alias or other properties is not possible (or impractical) to pre-code (Exits is an example of this). To create a command with a dynamic call signature, first define the command body normally in a class (set your key, aliases to default values), then use the following call (assuming the command class you created is named MyCommand):

11.8. Commands 137 Evennia Documentation, Release 0.9

cmd= MyCommand(key="newname", aliases=["test","test2"], locks="cmd:all()", ...)

All keyword arguments you give to the Command constructor will be stored as a property on the command object. This will overload existing properties defined on the parent class. Normally you would define your class and only overload things like key and aliases at run-time. But you could in principle also send method objects (like func) as keyword arguments in order to make your command completely customized at run-time.

11.8.7 Exits

Note: This is an advanced topic. Exits are examples of the use of a Dynamic Command.

The functionality of Exit objects in Evennia is not hard-coded in the engine. Instead Exits are normal typeclassed objects that auto-create a CmdSet on themselves when they load. This cmdset has a single dynamically created Command with the same properties (key, aliases and locks) as the Exit object itself. When entering the name of the exit, this dynamic exit-command is triggered and (after access checks) moves the Character to the exit’s destination. Whereas you could customize the Exit object and its command to achieve completely different behaviour, you will usually be fine just using the appropriate traverse_* hooks on the Exit object. But if you are interested in really changing how things work under the hood, check out evennia/objects/objects.py for how the Exit typeclass is set up.

11.8.8 Command instances are re-used

Note: This is an advanced topic that can be skipped when first learning about Commands. A Command class sitting on an object is instantiated once and then re-used. So if you run a command from object1 over and over you are in fact running the same command instance over and over (if you run the same command but sitting on object2 however, it will be a different instance). This is usually not something you’ll notice, since every time the Command-instance is used, all the relevant properties on it will be overwritten. But armed with this knowledge you can implement some of the more exotic command mechanism out there, like the command having a ‘memory’ of what you last entered so that you can back-reference the previous arguments etc. Note: On a server reload, all Commands are rebuilt and memory is flushed. To show this in practice, consider this command: class CmdTestID(Command): key="testid"

def func(self):

if not hasattr(self,"xval"): self.xval=0 self.xval+=1

self.caller.msg("Command memory ID: {} (xval={})".format(id(self), self.xval))

Adding this to the default character cmdset gives a result like this in-game:

138 Chapter 11. Server Components Evennia Documentation, Release 0.9

> testid Command memory ID: 140313967648552 (xval=1) > testid Command memory ID: 140313967648552 (xval=2) > testid Command memory ID: 140313967648552 (xval=3)

Note how the in-memory address of the testid command never changes, but xval keeps ticking up.

11.8.9 Dynamically created commands

This is also an advanced topic. Commands can also be created and added to a cmdset on the fly. Creating a class instance with a keyword argument, will assign that keyword argument as a property on this paricular command:

class MyCmdSet(CmdSet):

def at_cmdset_creation(self):

self.add(MyCommand(myvar=1, foo="test")

This will start the MyCommand with myvar and foo set as properties (accessable as self.myvar and self. foo). How they are used is up to the Command. Remember however the discussion from the previous section - since the Command instance is re-used, those properties will remain on the command as long as this cmdset and the object it sits is in memory (i.e. until the next reload). Unless myvar and foo are somehow reset when the command runs, they can be modified and that change will be remembered for subsequent uses of the command.

11.8.10 How commands actually work

Note: This is an advanced topic mainly of interest to server developers. Any time the user sends text to Evennia, the server tries to figure out if the text entered corresponds to a known command. This is how the command handler sequence looks for a logged-in user: 1. A user enters a string of text and presses enter. 2. The user’s Session determines the text is not some protocol-specific control sequence or OOB command, but sends it on to the command handler. 3. Evennia’s command handler analyzes the Session and grabs eventual references to Account and eventual pup- peted Characters (these will be stored on the command object later). The caller property is set appropriately. 4. If input is an empty string, resend command as CMD_NOINPUT. If no such command is found in cmdset, ignore. 5. If command.key matches settings.IDLE_COMMAND, update timers but don’t do anything more. 6. The command handler gathers the CmdSets available to caller at this time: • The caller’s own currently active CmdSet. • CmdSets defined on the current account, if caller is a puppeted object. • CmdSets defined on the Session itself. • The active CmdSets of eventual objects in the same location (if any). This includes commands on Exits. • Sets of dynamically created System commands representing available Communications.

11.8. Commands 139 Evennia Documentation, Release 0.9

7. All CmdSets of the same priority are merged together in groups. Grouping avoids order-dependent issues of merging multiple same-prio sets onto lower ones. 8. All the grouped CmdSets are merged in reverse priority into one combined CmdSet according to each set’s merge rules. 9. Evennia’s command parser takes the merged cmdset and matches each of its commands (using its key and aliases) against the beginning of the string entered by caller. This produces a set of candidates. 10. The cmd parser next rates the matches by how many characters they have and how many percent matches the respective known command. Only if candidates cannot be separated will it return multiple matches. • If multiple matches were returned, resend as CMD_MULTIMATCH. If no such command is found in cmdset, return hard-coded list of matches. • If no match was found, resend as CMD_NOMATCH. If no such command is found in cmdset, give hard- coded error message. 11. If a single command was found by the parser, the correct command object is plucked out of storage. This usually doesn’t mean a re-initialization. 12. It is checked that the caller actually has access to the command by validating the lockstring of the command. If not, it is not considered as a suitable match and CMD_NOMATCH is triggered. 13. If the new command is tagged as a channel-command, resend as CMD_CHANNEL. If no such command is found in cmdset, use hard-coded implementation. 14. Assign several useful variables to the command instance (see previous sections). 15. Call at_pre_command() on the command instance. 16. Call parse() on the command instance. This is fed the remainder of the string, after the name of the command. It’s intended to pre-parse the string into a form useful for the func() method. 17. Call func() on the command instance. This is the functional body of the command, actually doing useful things. 18. Call at_post_command() on the command instance.

11.8.11 Assorted notes

The return value of Command.func() is a Twisted deferred. Evennia does not use this return value at all by default. If you do, you must thus do so asynchronously, using callbacks.

# in command class func() def callback(ret, caller): caller.msg("Returned is %s"% ret) deferred= self.execute_command("longrunning") deferred.addCallback(callback, self.caller)

This is probably not relevant to any but the most advanced/exotic designs (one might use it to create a “nested” command structure for example). The save_for_next class variable can be used to implement state-persistent commands. For example it can make a command operate on “it”, where it is determined by what the previous command operated on.

140 Chapter 11. Server Components Evennia Documentation, Release 0.9

11.9 Command Sets

Command Sets are intimately linked with Commands and you should be familiar with Commands before reading this page. The two pages were split for ease of reading. A Command Set (often referred to as a CmdSet or cmdset) is the basic unit for storing one or more Commands.A given Command can go into any number of different command sets. Storing Command classes in a command set is the way to make commands available to use in your game. When storing a CmdSet on an object, you will make the commands in that command set available to the object. An example is the default command set stored on new Characters. This command set contains all the useful commands, from look and inventory to @dig and @reload (permissions then limit which players may use them, but that’s a separate topic). When an account enters a command, cmdsets from the Account, Character, its location, and elsewhere are pulled together into a merge stack. This stack is merged together in a specific order to create a single “merged” cmdset, representing the pool of commands available at that very moment. An example would be a Window object that has a cmdset with two commands in it: look through window and open window. The command set would be visible to players in the room with the window, allowing them to use those commands only there. You could imagine all sorts of clever uses of this, like a Television object which had multiple commands for looking at it, switching channels and so on. The tutorial world included with Evennia showcases a dark room that replaces certain critical commands with its own versions because the Character cannot see. If you want a quick start into defining your first commands and using them with command sets, you can head over to the Adding Command Tutorial which steps through things without the explanations.

11.9.1 Defining Command Sets

A CmdSet is, as most things in Evennia, defined as a Python class inheriting from the correct parent (evennia. CmdSet, which is a shortcut to evennia.commands.cmdset.CmdSet). The CmdSet class only needs to define one method, called at_cmdset_creation(). All other class parameters are optional, but are used for more advanced set manipulation and coding (see the merge rules section).

# file mygame/commands/mycmdset.py from evennia import CmdSet

# this is a theoretical custom module with commands we # created previously: mygame/commands/mycommands.py from commands import mycommands class MyCmdSet(CmdSet): def at_cmdset_creation(self): """ The only thing this method should need to do is to add commands to the set. """ self.add(mycommands.MyCommand1()) self.add(mycommands.MyCommand2()) self.add(mycommands.MyCommand3())

The CmdSet’s add() method can also take another CmdSet as input. In this case all the commands from that CmdSet will be appended to this one as if you added them line by line:

11.9. Command Sets 141 Evennia Documentation, Release 0.9

def at_cmdset_creation(): ... self.add(AdditionalCmdSet) # adds all command from this set ...

If you added your command to an existing cmdset (like to the default cmdset), that set is already loaded into memory. You need to make the server aware of the code changes:

@reload

You should now be able to use the command. If you created a new, fresh cmdset, this must be added to an object in order to make the commands within available. A simple way to temporarily test a cmdset on yourself is use the @py command to execute a python snippet:

@py self.cmdset.add('commands.mycmdset.MyCmdSet')

This will stay with you until you @reset or @shutdown the server, or you run

@py self.cmdset.delete('commands.mycmdset.MyCmdSet')

In the example above, a specific Cmdset class is removed. Calling delete without arguments will remove the latest added cmdset. Note: Command sets added using cmdset.add are, by default, not persistent in the database. If you want the cmdset to survive a reload, you can do:

@py self.cmdset.add(commands.mycmdset.MyCmdSet, permanent=True)

Or you could add the cmdset as the default cmdset:

@py self.cmdset.add_default(commands.mycmdset.MyCmdSet)

An object can only have one “default” cmdset (but can also have none). This is meant as a safe fall-back even if all other cmdsets fail or are removed. It is always persistent and will not be affected by cmdset.delete(). To remove a default cmdset you must explicitly call cmdset.remove_default(). Command sets are often added to an object in its at_object_creation method. For more examples of adding commands, read the Step by step tutorial. Generally you can customize which command sets are added to your objects by using self.cmdset.add() or self.cmdset.add_default(). Important: Commands are identified uniquely by key or alias (see Commands). If any overlap exists, two commands are considered identical. Adding a Command to a command set that already has an identical command will replace the previous command. This is very important. You must take this behavior into account when attempting to overload any default Evennia commands with your own. Otherwise, you may accidentally “hide” your own command in your command set when adding a new one that has a matching alias.

Properties on Command Sets

There are several extra flags that you can set on CmdSets in order to modify how they work. All are optional and will be set to defaults otherwise. Since many of these relate to merging cmdsets, you might want to read the Adding and Merging Command Sets section for some of these to make sense. • key (string) - an identifier for the cmdset. This is optional, but should be unique. It is used for display in lists, but also to identify special merging behaviours using the key_mergetype dictionary below. - mergetype (string) - allows for one of the following string values: “Union”, “Intersect”, “Replace”, or “Remove”.

142 Chapter 11. Server Components Evennia Documentation, Release 0.9

• priority (int) - This defines the merge order of the merge stack - cmdsets will merge in rising order of priority with the highest priority set merging last. During a merger, the commands from the set with the higher priority will have precedence (just what happens depends on the merge type). If priority is identical, the order in the merge stack determines preference. The priority value must be greater or equal to -100. Most in-game sets should usually have priorities between 0 and 100. Evennia default sets have priorities as follows (these can be changed if you want a different distribution): – EmptySet: -101 (should be lower than all other sets) – SessionCmdSet: -20 – AccountCmdSet: -10 – CharacterCmdSet: 0 – ExitCmdSet: 101 (generally should always be available) – ChannelCmdSet: 101 (should usually always be available) - since exits never accept arguments, there is no collision between exits named the same as a channel even though the commands “collide”. • key_mergetype (dict) - a dict of key:mergetype pairs. This allows this cmdset to merge differently with certain named cmdsets. If the cmdset to merge with has a key matching an entry in key_mergetype, it will not be merged according to the setting in mergetype but according to the mode in this dict. Please note that this is more complex than it may seem due to the merge order of command sets. Please review that section before using key_mergetype. • duplicates (bool/None default None) - this determines what happens when merging same-priority cmdsets containing same-key commands together. Thedupicate option will only apply when merging the cmdset with this option onto one other cmdset with the same priority. The resulting cmdset will not retain this duplicate setting. – None (default): No duplicates are allowed and the cmdset being merged “onto” the old one will take precedence. The result will be unique commands. However, the system will assume this value to be True for cmdsets on Objects, to avoid dangerous clashes. This is usually the safe bet. – False: Like None except the system will not auto-assume any value for cmdsets defined on Objects. – True: Same-named, same-prio commands will merge into the same cmdset. This will lead to a multimatch error (the user will get a list of possibilities in order to specify which command they meant). This is is useful e.g. for on-object cmdsets (example: There is a red button and a green button in the room. Both have a press button command, in cmdsets with the same priority. This flag makes sure that just writing press button will force the Player to define just which object’s command was intended). • no_objs this is a flag for the cmdhandler that builds the set of commands available at every moment. It tells the handler not to include cmdsets from objects around the account (nor from rooms or inventory) when building the merged set. Exit commands will still be included. This option can have three values: – None (default): Passthrough of any value set explicitly earlier in the merge stack. If never set explicitly, this acts as False. – True/False: Explicitly turn on/off. If two sets with explicit no_objs are merged, priority determines what is used. • no_exits - this is a flag for the cmdhandler that builds the set of commands available at every moment. It tells the handler not to include cmdsets from exits. This flag can have three values: – None (default): Passthrough of any value set explicitly earlier in the merge stack. If never set explicitly, this acts as False. – True/False: Explicitly turn on/off. If two sets with explicit no_exits are merged, priority determines what is used.

11.9. Command Sets 143 Evennia Documentation, Release 0.9

• no_channels (bool) - this is a flag for the cmdhandler that builds the set of commands available at every moment. It tells the handler not to include cmdsets from available in-game channels. This flag can have three values: – None (default): Passthrough of any value set explicitly earlier in the merge stack. If never set explicitly, this acts as False. – True/False: Explicitly turn on/off. If two sets with explicit no_channels are merged, priority deter- mines what is used.

11.9.2 Command Sets Searched

When a user issues a command, it is matched against the merged command sets available to the player at the moment. Which those are may change at any time (such as when the player walks into the room with the Window object described earlier). The currently valid command sets are collected from the following sources: • The cmdsets stored on the currently active Session. Default is the empty SessionCmdSet with merge priority -20. • The cmdsets defined on the Account. Default is the AccountCmdSet with merge priority -10. • All cmdsets on the Character/Object (assuming the Account is currently puppeting such a Character/Object). Merge priority 0. • The cmdsets of all objects carried by the puppeted Character (checks the call lock). Will not be included if no_objs option is active in the merge stack. • The cmdsets of the Character’s current location (checks the call lock). Will not be included if no_objs option is active in the merge stack. • The cmdsets of objects in the current location (checks the call lock). Will not be included if no_objs option is active in the merge stack. • The cmdsets of Exits in the location. Merge priority +101. Will not be included if no_exits or no_objs option is active in the merge stack. • The channel cmdset containing commands for posting to all channels the account or character is currently connected to. Merge priority +101. Will not be included if no_channels option is active in the merge stack. Note that an object does not have to share its commands with its surroundings. A Character’s cmdsets should not be shared for example, or all other Characters would get multi-match errors just by being in the same room. The ability of an object to share its cmdsets is managed by its call lock. For example, Character objects defaults to call:false() so that any cmdsets on them can only be accessed by themselves, not by other objects around them. Another example might be to lock an object with call:inside() to only make their commands available to objects inside them, or cmd:holds() to make their commands available only if they are held.

11.9.3 Adding and Merging Command Sets

Note: This is an advanced topic. It’s very useful to know about, but you might want to skip it if this is your first time learning about commands. CmdSets have the special ability that they can be merged together into new sets. Which of the ingoing commands end up in the merged set is defined by the merge rule and the relative priorities of the two sets. Removing the latest added set will restore things back to the way it was before the addition.

144 Chapter 11. Server Components Evennia Documentation, Release 0.9

CmdSets are non-destructively stored in a stack inside the cmdset handler on the object. This stack is parsed to create the “combined” cmdset active at the moment. CmdSets from other sources are also included in the merger such as those on objects in the same room (like buttons to press) or those introduced by state changes (such as when entering a menu). The cmdsets are all ordered after priority and then merged together in reverse order. That is, the higher priority will be merged “onto” lower-prio ones. By defining a cmdset with a merge-priority between that of two other sets, you will make sure it will be merged in between them. The very first cmdset in this stack is called the Default cmdset and is protected from accidental deletion. Running obj.cmdset.delete() will never delete the default set. Instead one should add new cmdsets on top of the default to “hide” it, as described below. Use the special obj.cmdset.delete_default() only if you really know what you are doing.

CmdSet merging is an advanced feature useful for implementing powerful game effects. Imagine for example a player entering a dark room. You don’t want the player to be able to find everything in the room at a glance - maybe you even want them to have a hard time to find stuff in their backpack! You can then define a different CmdSet with commands that override the normal ones. While they are in the dark room, maybe the look and inv commands now just tell the player they cannot see anything! Another example would be to offer special combat commands only when the player is in combat. Or when being on a boat. Or when having taken the super power-up. All this can be done on the fly by merging command sets.

Merge Rules

Basic rule is that command sets are merged in reverse priority order. That is, lower-prio sets are merged first and higher prio sets are merged “on top” of them. Think of it like a layered cake with the highest priority on top. To further understand how sets merge, we need to define some examples. Let’s call the first command set A and the second B. We assume B is the command set already active on our object and we will merge A onto B. In code terms this would be done by object.cdmset.add(A). Remember, B is already active on object from before. We let the A set have higher priority than B. A priority is simply an integer number. As seen in the list above, Evennia’s default cmdsets have priorities in the range -101 to 120. You are usually safe to use a priority of 0 or 1 for most game effects. In our examples, both sets contain a number of commands which we’ll identify by numbers, like A1, A2 for set A and B1, B2, B3, B4 for B. So for that example both sets contain commands with the same keys (or aliases) “1” and “2” (this could for example be “look” and “get” in the real game), whereas commands 3 and 4 are unique to B. To describe a merge between these sets, we would write A1,A2 + B1,B2,B3,B4 = ? where ? is a list of commands that depend on which merge type A has, and which relative priorities the two sets have. By convention, we read this statement as “New command set A is merged onto the old command set B to form ?”. Below are the available merge types and how they work. Names are partly borrowed from Set theory. • Union (default) - The two cmdsets are merged so that as many commands as possible from each cmdset ends up in the merged cmdset. Same-key commands are merged by priority.

# Union A1,A2+ B1,B2,B3,B4= A1,A2,B3,B4

• Intersect - Only commands found in both cmdsets (i.e. which have the same keys) end up in the merged cmdset, with the higher-priority cmdset replacing the lower one’s commands.

# Intersect A1,A3,A5+ B1,B2,B4,B5= A1,A5

• Replace - The commands of the higher-prio cmdset completely replaces the lower-priority cmdset’s commands, regardless of if same-key commands exist or not.

11.9. Command Sets 145 Evennia Documentation, Release 0.9

# Replace A1,A3+ B1,B2,B4,B5= A1,A3

• Remove - The high-priority command sets removes same-key commands from the lower-priority cmdset. They are not replaced with anything, so this is a sort of filter that prunes the low-prio set using the high-prio one as a template.

# Remove A1,A3+ B1,B2,B3,B4,B5= B2,B4,B5

Besides priority and mergetype, a command-set also takes a few other variables to control how they merge: • duplicates (bool) - determines what happens when two sets of equal priority merge. Default is that the new set in the merger (i.e. A above) automatically takes precedence. But if duplicates is true, the result will be a merger with more than one of each name match. This will usually lead to the player receiving a multiple-match error higher up the road, but can be good for things like cmdsets on non-player objects in a room, to allow the system to warn that more than one ‘ball’ in the room has the same ‘kick’ command defined on it and offer a chance to select which ball to kick . . . Allowing duplicates only makes sense for Union and Intersect, the setting is ignored for the other mergetypes. • key_mergetypes (dict) - allows the cmdset to define a unique mergetype for particular cmdsets, identified by their cmdset key. Format is {CmdSetkey:mergetype}. Example: {'Myevilcmdset','Replace'} which would make sure for this set to always use ‘Replace’ on the cmdset with the key Myevilcmdset only, no matter what the main mergetype is set to. Warning: The key_mergetypes dictionary can only work on the cmdset we merge onto. When using key_mergetypes it is thus important to consider the merge priorities - you must make sure that you pick a priority between the cmdset you want to detect and the next higher one, if any. That is, if we define a cmdset with a high priority and set it to affect a cmdset that is far down in the merge stack, we would not “see” that set when it’s time for us to merge. Example: Merge stack is A(prio=-10), B(prio=-5), C(prio=0), D(prio=5). We now merge a cmdset E(prio=10) onto this stack, with a key_mergetype={"B":"Replace"}. But priorities dictate that we won’t be merged onto B, we will be merged onto E (which is a merger of the lower-prio sets at this point). Since we are merging onto E and not B, our key_mergetype directive won’t trigger. To make sure it works we must make sure we merge onto B. Setting E’s priority to, say, -4 will make sure to merge it onto B and affect it appropriately. More advanced cmdset example:

from commands import mycommands

class MyCmdSet(CmdSet):

key="MyCmdSet" priority=4 mergetype="Replace" key_mergetypes={'MyOtherCmdSet':'Union'}

def at_cmdset_creation(self): """ The only thing this method should need to do is to add commands to the set. """ self.add(mycommands.MyCommand1()) self.add(mycommands.MyCommand2()) self.add(mycommands.MyCommand3())

146 Chapter 11. Server Components Evennia Documentation, Release 0.9

Assorted Notes

It is very important to remember that two commands are compared both by their key properties and by their aliases properties. If either keys or one of their aliases match, the two commands are considered the same. So consider these two Commands: • A Command with key “kick” and alias “fight” • A Command with key “punch” also with an alias “fight” During the cmdset merging (which happens all the time since also things like channel commands and exits are merged in), these two commands will be considered identical since they share alias. It means only one of them will remain after the merger. Each will also be compared with all other commands having any combination of the keys and/or aliases “kick”, “punch” or “fight”. . . . So avoid duplicate aliases, it will only cause confusion.

11.10 Typeclasses

Typeclasses form the core of Evennia data storage. It allows Evennia to represent any number of different game entities as Python classes, without having to modify the database schema for every new type. In Evennia the most important game entities, Accounts, Objects, Scripts and Channels are all Python classes inheriting, at varying distance, from evennia.typeclasses.models.TypedObject. In the documentation we refer to these objects as being “typeclassed” or even “being a typeclass”. This is how the inheritance looks for the typeclasses in Evennia:

TypedObject ______|______|||| 1: AccountDB ObjectDB ScriptDB ChannelDB |||| 2: DefaultAccount DefaultObject DefaultScript DefaultChannel | DefaultCharacter|| | DefaultRoom|| | DefaultExit|| |||| 3: Account Object Script Channel Character Room Exit

• Level 1 above is the “database model” level. This describes the database tables and fields (this is technically a Django model). • Level 2 is where we find Evennia’s default implementations of the various game entities, on top of the database. These classes define all the hook methods that Evennia calls in various situations. DefaultObject is a little special since it’s the parent for DefaultCharacter, DefaultRoom and DefaultExit. They are all grouped under level 2 because they all represents defaults to build from. • Level 3, finally, holds empty template classes created in your game directory. This is the level you are meant to modify and tweak as you please, overloading the defaults as befits your game. The templates inherit directly from their defaults, so Object inherits from DefaultObject and Room inherits from DefaultRoom.

The typeclass/list command will provide a list of all typeclasses known to

11.10. Typeclasses 147 Evennia Documentation, Release 0.9

Evennia. This can be useful for getting a feel for what is available. Note however that if you add a new module with a class in it but do not import that module from anywhere, the typeclass/list will not find it. To make it known to Evennia you must import that module from somewhere.

11.10.1 Difference between typeclasses and classes

All Evennia classes inheriting from class in the table above share one important feature and two important limitations. This is why we don’t simply call them “classes” but “typeclasses”. 1. A typeclass can save itself to the database. This means that some properties (actually not that many) on the class actually represents database fields and can only hold very specific data types. This is detailed below. 2. Due to its connection to the database, the typeclass’ name must be unique across the entire server namespace. That is, there must never be two same-named classes defined anywhere. So the below code would give an error (since DefaultObject is now globally found both in this module and in the default library):

from evennia import DefaultObject as BaseObject class DefaultObject(BaseObject): pass

3. A typeclass’ __init__ method should normally not be overloaded. This has mostly to do with the fact that the __init__ method is not called in a predictable way. Instead Evennia suggest you use the at_*_creation hooks (like at_object_creation for Objects) for setting things the very first time the typeclass is saved to the database or the at_init hook which is called every time the object is cached to memory. If you know what you are doing and want to use __init__, it must both accept arbitrary keyword arguments and use super to call its parent:

.. code:: python

def __init__(self,**kwargs): # my content super().__init__(**kwargs) # my content

Apart from this, a typeclass works like any normal Python class and you can treat it as such.

Creating a new typeclass

It’s easy to work with Typeclasses. Either you use an existing typeclass or you create a new Python class inheriting from an existing typeclass. Here is an example of creating a new type of Object:

from evennia import DefaultObject

class Furniture(DefaultObject): # this defines what 'furniture' is, like # storing who sits on it or something. pass

You can now create a new Furniture object in two ways. First (and usually not the most convenient) way is to create an instance of the class and then save it manually to the database:

chair= Furniture(db_key="Chair") chair.save()

148 Chapter 11. Server Components Evennia Documentation, Release 0.9

To use this you must give the database field names as keywords to the call. Which are available depends on the entity you are creating, but all start with db_* in Evennia. This is a method you may be familiar with if you know Django from before. It is recommended that you instead use the create_* functions to create typeclassed entities:

from evennia import create_object

chair= create_object(Furniture, key="Chair") # or (if your typeclass is in a module furniture.py) chair= create_object("furniture.Furniture", key="Chair")

The create_object (create_account, create_script etc) takes the typeclass as its first argument; this can both be the actual class or the python path to the typeclass as found under your game directory. So if your Furniture typeclass sits in mygame/typeclasses/furniture.py, you could point to it as typeclasses.furniture.Furniture. Since Evennia will itself look in mygame/typeclasses, you can shorten this even further to just furniture.Furniture. The create-functions take a lot of extra keywords allow- ing you to set things like Attributes and Tags all in one go. These keywords don’t use the db_* prefix. This will also automatically save the new instance to the database, so you don’t need to call save() explicitly.

11.10.2 About typeclass properties

An example of a database field is db_key. This stores the “name” of the entity you are modifying and can thus only hold a string. This is one way of making sure to update the db_key:

chair.db_key="Table" chair.save()

print(chair.db_key) <<< Table

That is, we change the chair object to have the db_key “Table”, then save this to the database. However, you almost never do things this way; Evennia defines property wrappers for all the database fields. These are named the same as the field, but without the db_ part:

chair.key="Table"

print(chair.key) <<< Table

The key wrapper is not only shorter to write, it will make sure to save the field for you, and does so more efficiently by levering update mechanics under the hood. So whereas it is good to be aware that the field is named db_key you should use key as much as you can. Each typeclass entity has some unique fields relevant to that type. But all also share the following fields (the wrapper name without db_ is given): • key (str): The main identifier for the entity, like “Rose”, “myscript” or “Paul”. name is an alias. • date_created (datetime): Time stamp when this object was created. • typeclass_path (str): A python path pointing to the location of this (type)class There is one special field that doesn’t use the db_ prefix (it’s defined by Django): • id (int): the database id (database ref) of the object. This is an ever-increasing, unique integer. It can also be accessed as dbid (database ID) or pk (primary key). The dbref property returns the string form “#id”. The typeclassed entity has several common handlers:

11.10. Typeclasses 149 Evennia Documentation, Release 0.9

• tags - the TagHandler that handles tagging. Use tags.add() , tags.get() etc. • locks - the LockHandler that manages access restrictions. Use locks.add(), locks.get() etc. • attributes - the AttributeHandler that manages Attributes on the object. Use attributes.add() etc. • db (DataBase) - a shortcut property to the AttributeHandler; allowing obj.db.attrname = value • nattributes - the Non-persistent AttributeHandler for attributes not saved in the database. • ndb (NotDataBase) - a shortcut property to the Non-peristent AttributeHandler. Allows obj.ndb.attrname = value Each of the typeclassed entities then extend this list with their own properties. Go to the respective pages for Objects, Scripts, Accounts and Channels for more info. It’s also recommended that you explore the available entities using Evennia’s flat API to explore which properties and methods they have available.

11.10.3 Overloading hooks

The way to customize typeclasses is usually to overload hook methods on them. Hooks are methods that Evennia call in various situations. An example is the at_object_creation hook on Objects, which is only called once, the very first time this object is saved to the database. Other examples are the at_login hook of Accounts and the at_repeat hook of Scripts.

11.10.4 Querying for typeclasses

Most of the time you search for objects in the database by using convenience methods like the caller.search() of Commands or the search functions like evennia.search_objects. You can however also query for them directly using Django’s query language. This makes use of a database manager that sits on all typeclasses, named objects. This manager holds methods that allow database searches against that particular type of object (this is the way Django normally works too). When using Django queries, you need to use the full field names (like db_key) to search:

matches= Furniture.objects.get(db_key="Chair")

It is important that this will only find objects inheriting directly from Furniture in your database. If there was a subclass of Furniture named Sitables you would not find any chairs derived from Sitables with this query (this is not a Django feature but special to Evennia). To find objects from subclasses Evennia instead makes the get_family and filter_family query methods available:

# search for all furnitures and subclasses of furnitures # whose names starts with "Chair" matches= Furniture.objects.filter_family(db_key__startswith="Chair")

To make sure to search, say, all Scripts regardless of typeclass, you need to query from the database model itself. So for Objects, this would be ObjectDB in the diagram above. Here’s an example for Scripts:

from evennia import ScriptDB matches= ScriptDB.objects.filter(db_key__contains="Combat")

When querying from the database model parent you don’t need to use filter_family or get_family - you will always query all children on the database model.

150 Chapter 11. Server Components Evennia Documentation, Release 0.9

Updating existing typeclass instances

If you already have created instances of Typeclasses, you can modify the Python code at any time - due to how Python inheritance works your changes will automatically be applied to all children once you have reloaded the server. However, database-saved data, like db_* fields, Attributes, Tags etc, are not themselves embedded into the class and will not be updated automatically. This you need to manage yourself, by searching for all relevant objects and updating or adding the data:

# add a worth Attribute to all existing Furniture for obj in Furniture.objects.all(): # this will loop over all Furniture instances obj.db.worth= 100

A common use case is putting all Attributes in the at_*_creation hook of the entity, such as at_object_creation for Objects. This is called every time an object is created - and only then. This is usually what you want but it does mean already existing objects won’t get updated if you change the contents of at_object_creation later. You can fix this in a similar way as above (manually setting each Attribute) or with something like this:

# Re-run at_object_creation only on those objects not having the new Attribute for obj in Furniture.objects.all(): if not obj.db.worth: obj.at_object_creation()

The above examples can be run in the command prompt created by evennia shell. You could also run it all in-game using @py. That however requires you to put the code (including imports) as one single line using ; and list comprehensions, like this (ignore the line break, that’s only for readability in the wiki):

@py from typeclasses.furniture import Furniture; [obj.at_object_creation() for obj in Furniture.objects.all() if not obj.db.worth]

It is recommended that you plan your game properly before starting to build, to avoid having to retroactively update objects more than necessary.

Swap typeclass

If you want to swap an already existing typeclass, there are two ways to do so: From in-game and via code. From inside the game you can use the default @typeclass command:

@typeclass objname= path.to.new.typeclass

There are two important switches to this command: • /reset - This will purge all existing Attributes on the object and re-run the creation hook (like at_object_creation for Objects). This assures you get an object which is purely of this new class. • /force - This is required if you are changing the class to be the same class the object already has - it’s a safety check to avoid user errors. This is usually used together with /reset to re-run the creation hook on an existing class. In code you instead use the swap_typeclass method which you can find on all typeclassed entities: obj_to_change.swap_typeclass(new_typeclass_path, clean_attributes=False, run_start_hooks="all", no_default=True, clean_cmdsets=False)

The arguments to this method are described in the API docs here.

11.10. Typeclasses 151 Evennia Documentation, Release 0.9

How typeclasses actually work

This is considered an advanced section. Technically, typeclasses are Django proxy models. The only database models that are “real” in the typeclass system (that is, are represented by actual tables in the database) are AccountDB, ObjectDB, ScriptDB and ChannelDB (there are also Attributes and Tags but they are not typeclasses themselves). All the subclasses of them are “proxies”, extending them with Python code without actually modifying the database layout. Evennia modifies Django’s proxy model in various ways to allow them to work without any boiler plate (for example you don’t need to set the Django “proxy” property in the model Meta subclass, Evennia handles this for you using metaclasses). Evennia also makes sure you can query subclasses as well as patches django to allow multiple inheritance from the same base class.

11.10.5 Caveats

Evennia uses the idmapper to cache its typeclasses (Django proxy models) in memory. The idmapper allows things like on-object handlers and properties to be stored on typeclass instances and to not get lost as long as the server is running (they will only be cleared on a Server reload). Django does not work like this by default; by default every time you search for an object in the database you’ll get a different instance of that object back and anything you stored on it that was not in the database would be lost. The bottom line is that Evennia’s Typeclass instances subside in memory a lot longer than vanilla Django model instance do. There is one caveat to consider with this, and that relates to making your own models: Foreign relationships to typeclasses are cached by Django and that means that if you were to change an object in a foreign relationship via some other means than via that relationship, the object seeing the relationship may not reliably update but will still see its old cached version. Due to typeclasses staying so long in memory, stale caches of such relationships could be more visible than common in Django. See the closed issue #1098 and its comments for examples and solutions.

11.11 Objects

All in-game objects in Evennia, be it characters, chairs, monsters, rooms or hand grenades are represented by an Evennia Object. Objects form the core of Evennia and is probably what you’ll spend most time working with. Objects are Typeclassed entities.

11.11.1 How to create your own object types

An Evennia Object is, per definition, a Python class that includes evennia.DefaultObject among its parents. In mygame/typeclasses/objects.py there is already a class Object that inherits from DefaultObject and that you can inherit from. You can put your new typeclass directly in that module or you could organize your code in some other way. Here we assume we make a new module mygame/typeclasses/flowers.py:

# mygame/typeclasses/flowers.py from typeclasses.objects import Object class Rose(Object): """ This creates a simple rose object """ def at_object_creation(self): "this is called only once, when object is first created" (continues on next page)

152 Chapter 11. Server Components Evennia Documentation, Release 0.9

(continued from previous page) # add a persistent attribute 'desc' # to object (silly example). self.db.desc="This is a pretty rose with thorns."

You could save this in the mygame/typeclasses/objects.py (then you’d not need to import Object) or you can put it in a new module. Let’s say we do the latter, making a module typeclasses/flowers.py. Now you just need to point to the class Rose with the @create command to make a new rose:

@create/drop MyRose:flowers.Rose

What the @create command actually does is to use evennia.create_object. You can do the same thing yourself in code: from evennia import create_object new_rose= create_object("typeclasses.flowers.Rose", key="MyRose")

(The @create command will auto-append the most likely path to your typeclass, if you enter the call manually you have to give the full path to the class. The create.create_object function is powerful and should be used for all coded object creating (so this is what you use when defining your own building commands). Check out the ev.create_* functions for how to build other entities like Scripts). This particular Rose class doesn’t really do much, all it does it make sure the attribute desc(which is what the look command looks for) is pre-set, which is pretty pointless since you will usually want to change this at build time (using the @desc command or using the Spawner). The Object typeclass offers many more hooks that is available to use though - see next section.

11.11.2 Properties and functions on Objects

Beyond the properties assigned to all typeclassed objects (see that page for a list of those), the Object also has the following custom properties: • aliases - a handler that allows you to add and remove aliases from this object. Use aliases.add() to add a new alias and aliases.remove() to remove one. • location - a reference to the object currently containing this object. • home is a backup location. The main motivation is to have a safe place to move the object to if its location is destroyed. All objects should usually have a home location for safety. • destination - this holds a reference to another object this object links to in some way. Its main use is for Exits, it’s otherwise usually unset. • nicks - as opposed to aliases, a Nick holds a convenient nickname replacement for a real name, word or sequence, only valid for this object. This mainly makes sense if the Object is used as a game character - it can then store briefer shorts, example so as to quickly reference game commands or other characters. Use nicks.add(alias, realname) to add a new one. • account - this holds a reference to a connected Account controlling this object (if any). Note that this is set also if the controlling account is not currently online - to test if an account is online, use the has_account property instead. • sessions - if account field is set and the account is online, this is a list of all active sessions (server connections) to contact them through (it may be more than one if multiple connections are allowed in settings). • has_account - a shorthand for checking if an online account is currently connected to this object. • contents - this returns a list referencing all objects ‘inside’ this object (i,e. which has this object set as their location).

11.11. Objects 153 Evennia Documentation, Release 0.9

• exits - this returns all objects inside this object that are Exits, that is, has the destination property set. The last two properties are special: • cmdset - this is a handler that stores all command sets defined on the object (if any). • scripts - this is a handler that manages Scripts attached to the object (if any). The Object also has a host of useful utility functions. See the function headers in src/objects/objects.py for their arguments and more details. • msg() - this function is used to send messages from the server to an account connected to this object. • msg_contents() - calls msg on all objects inside this object. • search() - this is a convenient shorthand to search for a specific object, at a given location or globally. It’s mainly useful when defining commands (in which case the object executing the command is named caller and one can do caller.search() to find objects in the room to operate on). • execute_cmd() - Lets the object execute the given string as if it was given on the command line. • move_to - perform a full move of this object to a new location. This is the main move method and will call all relevant hooks, do all checks etc. • clear_exits() - will delete all Exits to and from this object. • clear_contents() - this will not delete anything, but rather move all contents (except Exits) to their des- ignated Home locations. • delete() - deletes this object, first calling clear_exits() and clear_contents(). The Object Typeclass defines many more hook methods beyond at_object_creation. Evennia calls these hooks at various points. When implementing your custom objects, you will inherit from the base parent and overload these hooks with your own custom code. See evennia.objects.objects for an updated list of all the available hooks or the API for DefaultObject here.

11.11.3 Subclasses of Object

There are three special subclasses of Object in default Evennia - Characters, Rooms and Exits. The reason they are separated is because these particular object types are fundamental, something you will always need and in some cases requires some extra attention in order to be recognized by the game engine (there is nothing stopping you from redefining them though). In practice they are all pretty similar to the base Object.

Characters

Characters are objects controlled by Accounts. When a new Account logs in to Evennia for the first time, a new Character object is created and the Account object is assigned to the account attribute. A Character object must have a Default Commandset set on itself at creation, or the account will not be able to issue any commands! If you just inherit your own class from evennia.DefaultCharacter and make sure to use super() to call the parent methods you should be fine. In mygame/typeclasses/characters.py is an empty Character class ready for you to modify.

154 Chapter 11. Server Components Evennia Documentation, Release 0.9

Rooms

Rooms are the root containers of all other objects. The only thing really separating a room from any other object is that they have no location of their own and that default commands like @dig creates objects of this class - so if you want to expand your rooms with more functionality, just inherit from ev.DefaultRoom. In mygame/ typeclasses/rooms.py is an empty Room class ready for you to modify.

Exits

Exits are objects connecting other objects (usually Rooms) together. An object named North or in might be an exit, as well as door, portal or jump out the window. An exit has two things that separate them from other objects. Firstly, their destination property is set and points to a valid object. This fact makes it easy and fast to locate exits in the database. Secondly, exits define a special Transit Command on themselves when they are created. This command is named the same as the exit object and will, when called, handle the practicalities of moving the character to the Exits’s destination - this allows you to just enter the name of the exit on its own to move around, just as you would expect. The exit functionality is all defined on the Exit typeclass, so you could in principle completely change how exits work in your game (it’s not recommended though, unless you really know what you are doing). Exits are locked using an access_type called traverse and also make use of a few hook methods for giving feedback if the traversal fails. See evennia.DefaultExit for more info. In mygame/typeclasses/exits.py there is an empty Exit class for you to modify. The process of traversing an exit is as follows: 1. The traversing obj sends a command that matches the Exit-command name on the Exit object. The cmdhandler detects this and triggers the command defined on the Exit. Traversal always involves the “source” (the current location) and the destination (this is stored on the Exit object). 2. The Exit command checks the traverse lock on the Exit object 3. The Exit command triggers at_traverse(obj, destination) on the Exit object. 4. In at_traverse, object.move_to(destination) is triggered. This triggers the following hooks, in order: 1. obj.at_before_move(destination) - if this returns False, move is aborted. 2. origin.at_before_leave(obj, destination) 3. obj.announce_move_from(destination) 4. Move is performed by changing obj.location from source location to destination. 5. obj.announce_move_to(source) 6. destination.at_object_receive(obj, source) 7. obj.at_after_move(source) 5. On the Exit object, at_after_traverse(obj, source) is triggered.

If the move fails for whatever reason, the Exit will look for an Attribute err_traverse on itself and display this as an error message. If this is not found, the Exit will instead call at_failed_traverse(obj) on itself.

11.11. Objects 155 Evennia Documentation, Release 0.9

11.12 Scripts

Scripts are the out-of-character siblings to the in-character Objects. Scripts are so flexible that the “Script” is a bit limiting

• we had to pick something to name them after all. Other possible names (depending on what you’d use them for) would be OOBObjects, StorageContainers or TimerObjects. Scripts can be used for many different things in Evennia: • They can attach to Objects to influence them in various ways - or exist independently of any one in-game entity (so-called Global Scripts). • They can work as timers and tickers - anything that may change with Time. But they can also have no time dependence at all. Note though that if all you want is just to have an object method called repeatedly, you should consider using the TickerHandler which is more limited but is specialized on just this task. • They can describe State changes. A Script is an excellent platform for hosting a persistent, but unique system handler. For example, a Script could be used as the base to track the state of a turn-based combat system. Since Scripts can also operate on a timer they can also update themselves regularly to perform various actions. • They can act as data stores for storing game data persistently in the database (thanks to its ability to have Attributes). • They can be used as OOC stores for sharing data between groups of objects, for example for tracking the turns in a turn-based combat system or barter exchange.

Scripts are Typeclassed entities and are manipulated in a similar way to how it works for other such Evennia entities:

# create a new script new_script= evennia.create_script(key="myscript", typeclass=...)

# search (this is always a list, also if there is only one match) list_of_myscript= evennia.search_script("myscript")

11.12.1 Defining new Scripts

A Script is defined as a class and is created in the same way as other typeclassed entities. The class has several properties to control the timer-component of the scripts. These are all optional - leaving them out will just create a Script with no timer components (useful to act as a database store or to hold a persistent game system, for example).

This you can do for example in the module evennia/typeclasses/scripts.py. Below is an example Script Typeclass.

156 Chapter 11. Server Components Evennia Documentation, Release 0.9

from evennia import DefaultScript class MyScript(DefaultScript):

def at_script_creation(self): self.key="myscript" self.interval= 60 # 1 min repeat

def at_repeat(self): # do stuff every minute

In mygame/typeclasses/scripts.py is the Script class which inherits from DefaultScript already. This is provided as your own base class to do with what you like: You can tweak Script if you want to change the default behavior and it is usually convenient to inherit from this instead. Here’s an example:

# for example in mygame/typeclasses/scripts.py # Script class is defined at the top of this module import random class Weather(Script): """ A timer script that displays weather info. Meant to be attached to a room.

""" def at_script_creation(self): self.key="weather_script" self.desc="Gives random weather messages." self.interval= 60 * 5 # every 5 minutes self.persistent= True # will survive reload

def at_repeat(self): "called every self.interval seconds." rand= random.random() if rand< 0.5: weather="A faint breeze is felt." elif rand< 0.7: weather="Clouds sweep across the sky." else: weather="There is a light drizzle of rain." # send this message to everyone inside the object this # script is attached to (likely a room) self.obj.msg_contents(weather)

If we put this script on a room, it will randomly report some weather to everyone in the room every 5 minutes.

To activate it, just add it to the script handler (scripts) on an Room. That object becomes self.obj in the example above. Here we put it on a room called myroom:

11.12. Scripts 157 Evennia Documentation, Release 0.9

myroom.scripts.add(scripts.Weather)

| Note that ``typeclasses`` in your game dir is added to the setting ``TYPECLASS_PATHS``. | Therefore we don’t need to give the full path (``typeclasses.scripts.Weather`` | but only ``scripts.Weather`` above.

You can also create scripts using the evennia.create_script function: from evennia import create_script create_script('typeclasses.weather.Weather', obj=myroom)

Note that if you were to give a keyword argument to create_script, that would override the default value in your Typeclass. So for example, here is an instance of the weather script that runs every 10 minutes instead (and also not survive a server reload):

create_script('typeclasses.weather.Weather', obj=myroom, persistent=False, interval=10*60)

From in-game you can use the @script command to launch the Script on things:

@script here= typeclasses.scripts.Weather

You can conveniently view and kill running Scripts by using the @scripts command in-game.

11.12.2 Properties and functions defined on Scripts

A Script has all the properties of a typeclassed object, such as db and ndb(see Typeclasses). Setting key is useful in order to manage scripts (delete them by name etc). These are usually set up in the Script’s typeclass, but can also be assigned on the fly as keyword arguments to evennia.create_script. • desc - an optional description of the script’s function. Seen in script listings. • interval - how often the script should run. If interval == 0 (default), this script has no timing com- ponent, will not repeat and will exist forever. This is useful for Scripts used for storage or acting as bases for various non-time dependent game systems. • start_delay - (bool), if we should wait interval seconds before firing for the first time or not. • repeats - How many times we should repeat, assuming interval > 0. If repeats is set to <= 0, the script will repeat indefinitely. Note that each firing of the script (including the first one) counts towards this value. So a Script with start_delay=False and repeats=1 will start, immediately fire and shut down right away. • persistent- if this script should survive a server reset or server shutdown. (You don’t need to set this for it to survive a normal reload - the script will be paused and seamlessly restart after the reload is complete). There is one special property:

158 Chapter 11. Server Components Evennia Documentation, Release 0.9

• obj - the Object this script is attached to (if any). You should not need to set this manually. If you add the script to the Object with myobj.scripts.add(myscriptpath) or give myobj as an argument to the utils.create.create_script function, the obj property will be set to myobj for you.

It’s also imperative to know the hook functions. Normally, overriding these are all the customization you’ll need to do in Scripts. You can find longer descriptions of these in src/scripts/scripts.py.

• at_script_creation() - this is usually where the script class sets things like interval and repeats; things that control how the script runs. It is only called once - when the script is first created. • is_valid() - determines if the script should still be running or not. This is called when running obj. scripts.validate(), which you can run manually, but which is also called by Evennia during certain situations such as reloads. This is also useful for using scripts as state managers. If the method returns False, the script is stopped and cleanly removed. • at_start() - this is called when the script starts or is unpaused. For persistent scripts this is at least once ever server startup. Note that this will always be called right away, also if start_delay is True. • at_repeat() - this is called every interval seconds, or not at all. It is called right away at startup, unless start_delay is True, in which case the system will wait interval seconds before calling. • at_stop() - this is called when the script stops for whatever reason. It’s a good place to do custom cleanup. • at_server_reload() - this is called whenever the server is warm-rebooted (e.g. with the @reload com- mand). It’s a good place to save non-persistent data you might want to survive a reload. • at_server_shutdown() - this is called when a system reset or systems shutdown is invoked. Running methods (usually called automatically by the engine, but possible to also invoke manually) • start() - this will start the script. This is called automatically whenever you add a new script to a handler. at_start() will be called. • stop() - this will stop the script and delete it. Removing a script from a handler will stop it automatically. at_stop() will be called. • pause() - this pauses a running script, rendering it inactive, but not deleting it. All properties are saved and timers can be resumed. This is called automatically when the server reloads and will not lead to the at_stop() hook being called. This is a suspension of the script, not a change of state. • unpause() - resumes a previously paused script. The at_start() hook will be called to allow it to reclaim its internal state. Timers etc are restored to what they were before pause. The server automatically unpauses all paused scripts after a server reload. • force_repeat() - this will forcibly step the script, regardless of when it would otherwise have fired. The timer will reset and the at_repeat() hook is called as normal. This also counts towards the total number of repeats, if limited. • time_until_next_repeat() - for timed scripts, this returns the time in seconds until it next fires. Returns None if interval==0. • remaining_repeats() - if the Script should run a limited amount of times, this tells us how many are currently left. • reset_callcount(value=0) - this allows you to reset the number of times the Script has fired. It only makes sense if repeats > 0. • restart(interval=None, repeats=None, start_delay=None) - this method allows you to restart the Script in-place with different run settings. If you do, the at_stop hook will be called and the

11.12. Scripts 159 Evennia Documentation, Release 0.9

Script brought to a halt, then the at_start hook will be called as the Script starts up with your (possibly changed) settings. Any keyword left at None means to not change the original setting.

11.12.3 Global Scripts

A script does not have to be connected to an in-game object. If not it is called a Global script. You can create global scripts by simply not supplying an object to store it on:

# adding a global script from evennia import create_script create_script("typeclasses.globals.MyGlobalEconomy", key="economy", persistent=True, obj=None)

Henceforth you can then get it back by searching for its key or other identifier with evennia.search_script. In-game, the scripts command will show all scripts.

Evennia supplies a convenient “container” called GLOBAL_SCRIPTS that can offer an easy way to access global scripts. If you know the name (key) of the script you can get it like so:

from evennia import GLOBAL_SCRIPTS my_script = GLOBAL_SCRIPTS.my_script # needed if there are spaces in name or name determined on the fly another_script = GLOBAL_SCRIPTS.get("another script") # get all global scripts (this returns a Queryset) all_scripts = GLOBAL_SCRIPTS.all() # you can operate directly on the script GLOBAL_SCRIPTS.weather.db.current_weather = "Cloudy"

| Note that global scripts appear as properties on ``GLOBAL_SCRIPTS`` based on their ``key``. | If you were to create two global scripts with the same ``key`` (even with different typeclasses), | the ``GLOBAL_SCRIPTS`` container will only return one of them (which one depends on order in | the database). Best is to organize your scripts so that this does not happen. Otherwise, use ``evennia.search_script`` to get exactly the script you want.

There are two ways to make a script appear as a property on GLOBAL_SCRIPTS. The first is to manually create a new global script with create_script as mentioned above. Often you want this to happen automatically when the server starts though. For this you can use the setting GLOBAL_SCRIPTS:

GLOBAL_SCRIPTS={ "my_script":{ "typeclass":"scripts.Weather", (continues on next page)

160 Chapter 11. Server Components Evennia Documentation, Release 0.9

(continued from previous page) "repeats":-1, "interval": 50, "desc":"Weather script" "persistent": True }, "storagescript":{ "typeclass":"scripts.Storage", "persistent": True } }

Here the key (myscript and storagescript above) is required, all other fields are optional. If typeclass is not given, a script of type settings.BASE_SCRIPT_TYPECLASS is assumed. The keys related to timing and intervals are only needed if the script is timed.

Evennia will use the information in settings.GLOBAL_SCRIPTS to automatically create and start these scripts when the server starts (unless they already exist, based on their key). You need to reload the server before the setting is read and new scripts become available. You can then find the key you gave as properties on evennia.GLOBAL_SCRIPTS (such as evennia.GLOBAL_SCRIPTS.storagescript).

Note: Make sure that your Script typeclass does not have any critical errors. If so, you’ll see errors in your log and your Script will temporarily fall back to being a DefaultScript type. Moreover, a script defined this way is guaranteed to exist when you try to access it:

from evennia import GLOBAL_SCRIPTS # first stop the script GLOBAL_SCRIPTS.storagescript.stop() # running the `scripts` command now will show no storagescript # but below now it's recreated again! storage= GLOBAL_SCRIPTS.storagescript

That is, if the script is deleted, next time you get it from GLOBAL_SCRIPTS, it will use the information in settings to recreate it for you.

Note that if your goal with the Script is to store persistent data, you should set it as persistent=True, either in settings.GLOBAL_SCRIPTS or in the Scripts typeclass. Otherwise any data you wanted to store on it will be gone (since a new script of the same name is restarted instead).

11.12.4 Dealing with Errors

Errors inside an timed, executing script can sometimes be rather terse or point to parts of the execution mechanism that is hard to interpret. One way to make it easier to debug scripts is to import Evennia’s native logger and wrap your functions in a try/catch block. Evennia’s logger can show you where the traceback occurred in your script.

11.12. Scripts 161 Evennia Documentation, Release 0.9

from evennia.utils import logger class Weather(DefaultScript):

# [...]

def at_repeat(self):

try: # [...] code as above except Exception: # logs the error logger.log_trace()

11.12.5 Example of a timed script

In-game you can try out scripts using the @script command. In the evennia/contrib/tutorial_examples/bodyfunctions.py is a little example script that makes you do little ‘sounds’ at random intervals. Try the following to apply an example time-based script to your character.

> @script self = bodyfunctions.BodyFunctions

| Note: Since ``evennia/contrib/tutorial_examples`` is in the default setting | ``TYPECLASS_PATHS``, we only need to specify the final part of the path, | that is, ``bodyfunctions.BodyFunctions``.

If you want to inflict your flatulence script on another person, place or thing, try something like the following:

> @py self.location.search('matt').scripts.add('bodyfunctions.BodyFunctions')

Here’s how you stop it on yourself.

> @script/stop self= bodyfunctions.BodyFunctions

This will kill the script again. You can use the @scripts command to list all active scripts in the game, if any (there are none by default).

For another example of a Script in use, check out the ‘Turn Based Combat System tutorial‘_. tutorial: https://github.com/evennia/evennia/wiki/Turn%20based%20Combat%20System

162 Chapter 11. Server Components Evennia Documentation, Release 0.9

11.13 Accounts

All users (real people) that starts a game Session on Evennia are doing so through an object called Account. The Account object has no in-game representation, it represents a unique game account. In order to actually get on the game the Account must puppet an Object (normally a ‘Character‘_).

Exactly how many Sessions can interact with an Account and its Puppets at once is determined by Evennia’s MULTISESSION_MODE setting.

Apart from storing login information and other account-specific data, the Account object is what is chatting on Channels. It is also a good place to store Permissions to be consistent between different in-game characters as well as configuration options. The Account object also has its own CmdSet, the AccountCmdSet.

Logged into default evennia, you can use the ooc command to leave your current ‘character‘_ and go into OOC mode. You are quite limited in this mode, basically it works like a simple chat program. It acts as a staging area for switching between Characters (if your game supports that) or as a safety mode if your Character gets deleted. Use ic to attempt to (re)puppet a Character.

Note that the Account object can have, and often does have, a different set of Permissions from the Character they control. Normally you should put your permissions on the Account level - this will overrule permissions set on the Character level. For the permissions of the Character to come into play the default quell command can be used. This allows for exploring the game using a different permission set (but you can’t escalate your permissions this way - for hierarchical permissions like Builder, Admin etc, the lower of the permissions on the Character/Account will always be used).

11.13.1 How to create your own Account types

You will usually not want more than one Account typeclass for all new accounts (but you could in principle create a system that changes an account’s typeclass dynamically).

An Evennia Account is, per definition, a Python class that includes evennia.DefaultAccount among its parents. In mygame/typeclasses/accounts.py there is an empty class ready for you to modify. Evennia defaults to using this (it inherits directly from DefaultAccount).

Here’s an example of modifying the default Account class in code:

# in mygame/typeclasses/accounts.py from evennia import DefaultAccount (continues on next page)

11.13. Accounts 163 Evennia Documentation, Release 0.9

(continued from previous page)

class Account(DefaultAccount): # [...]

at_account_creation(self):"this is called only once, when account is first created" self.db.real_name= None # this is set later self.db.real_address = None #

˓→ " self.db.config_1= True # default config self.db.config_2 = False #

˓→ " self.db.config_3=1 #"

# ... whatever else our game needs to know ``` Reload the server with `reload`.

. . . However, if you use examine *self (the makes you examine your Account object rather than your Character), you won’t see your new Attributes yet. This is because at_account_creation is only called the very first time the Account is called and your Account object already exists (any new Accounts that connect will see them though). To update yourself you need to make sure to re-fire the hook on all the Accounts you have already created. Here is an example of how to do this using py:

py [account.at_account_creation() for account in evennia.managers.accounts. all()] You should now see the Attributes on yourself. If you wanted Evennia to default to a completely different Account class located elsewhere, you must point Evennia to it. Add BASE_ACCOUNT_TYPECLASS to your settings file, and give the python path to your custom class as its value. By default this points to typeclasses.accounts.Account, the empty template we used above.

11.13.2 Properties on Accounts

Beyond those properties assigned to all typeclassed objects (see Typeclasses), the Account also has the following custom properties:

• user - a unique link to a User Django object, representing the logged-in user. • obj - an alias for character. • name - an alias for user.username • sessions - an instance of ObjectSessionHandler managing all connected Sessions (physical connections) this object listens to (Note: In older versions of Evennia, this was a list). The so-called session-id (used in many places) is found as a property sessid on each Session instance. • is_superuser (bool: True/False) - if this account is a superuser. Special handlers: • cmdset - This holds all the current Commands of this Account. By default these are the commands found in the cmdset defined by settings.CMDSET_ACCOUNT.

164 Chapter 11. Server Components Evennia Documentation, Release 0.9

• nicks - This stores and handles Nicks, in the same way as nicks it works on Objects. For Accounts, nicks are primarily used to store custom aliases for Channels. Selection of special methods (see evennia.DefaultAccount for details): • get_puppet - get a currently puppeted object connected to the Account and a given session id, if any. • puppet_object - connect a session to a puppetable Object. • unpuppet_object - disconnect a session from a puppetable Object. • msg - send text to the Account • execute_cmd - runs a command as if this Account did it. • search - search for Accounts.

11.14 Communications

Apart from moving around in the game world and talking, players might need other forms of communication. This is offered by Evennia’s Comm system. Stock evennia implements a ‘MUX-like’ system of channels, but there is nothing stopping you from changing things to better suit your taste. Comms rely on two main database objects - Msg and Channel. There is also the TempMsg which mimics the API of a Msg but has no connection to the database.

11.14.1 Msg

The Msg object is the basic unit of communication in Evennia. A message works a little like an e-mail; it always has a sender (a Account) and one or more recipients. The recipients may be either other Accounts, or a Channel (see below). You can mix recipients to send the message to both Channels and Accounts if you like. Once created, a Msg is normally not changed. It is peristently saved in the database. This allows for comprehensive logging of communications. This could be useful for allowing senders/receivers to have ‘mailboxes’ with the messages they want to keep.

Properties defined on Msg

• senders - this is a reference to one or many Account or Objects (normally Characters) sending the message. This could also be an External Connection such as a message coming in over IRC/IMC2 (see below). There is usually only one sender, but the types can also be mixed in any combination. • receivers - a list of target Accounts, Objects (usually Characters) or Channels to send the message to. The types of receivers can be mixed in any combination. • header - this is a text field for storing a title or header for the message. • message - the actual text being sent. • date_sent - when message was sent (auto-created). • locks - a lock definition. • hide_from - this can optionally hold a list of objects, accounts or channels to hide this Msg from. This relationship is stored in the database primarily for optimization reasons, allowing for quickly post-filter out messages not intended for a given target. There is no in-game methods for setting this, it’s intended to be done in code.

11.14. Communications 165 Evennia Documentation, Release 0.9

You create new messages in code using evennia.create_message (or evennia.utils.create. create_message.)

11.14.2 TempMsg

evennia.comms.models also has TempMsg which mimics the API of Msg but is not connected to the database. TempMsgs are used by Evennia for channel messages by default. They can be used for any system expecting a Msg but when you don’t actually want to save anything.

11.14.3 Channels

Channels are Typeclassed entities, which mean they can be easily extended and their functionality modified. To change which channel typeclass Evennia uses, change settings.BASE_CHANNEL_TYPECLASS. Channels act as generic distributors of messages. Think of them as “switch boards” redistributing Msg or TempMsg objects. Internally they hold a list of “listening” objects and any Msg (or TempMsg) sent to the channel will be distributed out to all channel listeners. Channels have Locks to limit who may listen and/or send messages through them. The sending of text to a channel is handled by a dynamically created Command that always have the same name as the channel. This is created for each channel by the global ChannelHandler. The Channel command is added to the Account’s cmdset and normal command locks are used to determine which channels are possible to write to. When subscribing to a channel, you can then just write the channel name and the text to send. The default ChannelCommand (which can be customized by pointing settings.CHANNEL_COMMAND_CLASS to your own command), implements a few convenient features: • It only sends TempMsg objects. Instead of storing individual entries in the database it instead dumps channel output a file log in server/logs/channel_.log. This is mainly for practical reasons - we find one rarely need to query individual Msg objects at a later date. Just stupidly dumping the log to a file also means a lot less database overhead. • It adds a /history switch to view the 20 last messages in the channel. These are read from the end of the log file. One can also supply a line number to start further back in the file (but always 20 entries at a time). It’s used like this:

> public/history > public/history 35

There are two default channels created in stock Evennia - MudInfo and Public. MudInfo receives server-related messages meant for Admins whereas Public is open to everyone to chat on (all new accounts are automatically joined to it when logging in, it is useful for asking questions). The default channels are defined by the DEFAULT_CHANNELS list (see evennia/settings_default.py for more details). You create new channels with evennia.create_channel (or evennia.utils.create. create_channel). In code, messages are sent to a channel using the msg or tempmsg methods of channels:

channel.msg(msgobj, header=None, senders=None, persistent=True)

The argument msgobj can be either a string, a previously constructed Msg or a TempMsg - in the latter cases all the following keywords are ignored since the message objects already contains all this information. If msgobj is a string, the other keywords are used for creating a new Msg or TempMsg on the fly, depending on if persistent is set or not. By default, a TempMsg is emitted for channel communication (since the default ChannelCommand instead logs to a file).

166 Chapter 11. Server Components Evennia Documentation, Release 0.9

# assume we have a 'sender' object and a channel named 'mychan'

# manually sending a message to a channel mychan.msg("Hello!", senders=[sender])

Properties defined on Channel

• key - main name for channel • aliases - alternative native names for channels • desc - optional description of channel (seen in listings) • keep_log (bool) - if the channel should store messages (default) • locks -A lock definition. Channels normally use the access_types send, control and listen.

11.15 Signals

This is feature available from evennia 0.9 and onward.

There are multiple ways for you to plug in your own functionality into Evennia. The most common way to do so is through hooks - methods on typeclasses that gets called at particular events. Hooks are great when you want a game entity to behave a certain way when something happens to it. Signals complements hooks for cases when you want to easily attach new functionality without overriding things on the typeclass.

When certain events happen in Evennia, a Signal is fired. The idea is that you can “attach” any number of event-handlers to these signals. You can attach any number of handlers and they’ll all fire whenever any entity triggers the signal.

Evennia uses the Django Signal system.

11.15.1 Attaching a handler to a signal

First you create your handler def myhandler(sender, **kwargs): # do stuff

The **kwargs is mandatory. Then you attach it to the signal of your choice: from evennia.server import signals signals.SIGNAL_OBJECT_POST_CREATE.connect(myhandler)

11.15. Signals 167 Evennia Documentation, Release 0.9

This particular signal fires after (post) an Account has connected to the game. When that happens, myhandler will fire with the sender being the Account that just connected.

If you want to respond only to the effects of a specific entity you can do so like this:

from evennia import search_account from evennia import signals

account= search_account("foo")[0] signals.SIGNAL_ACCOUNT_POST_CONNECT.connect(myhandler, account)

11.15.2 Available signals

All signals (including some django-specific defaults) are available in the module evennia.server.signals (with a shortcut evennia.signals). Signals are named by the sender type. So SIGNAL_ACCOUNT_* returns Account instances as senders, SIGNAL_OBJECT_* returns Objects etc. Extra keywords (kwargs) should be extracted from the **kwargs dict in the signal handler.

• SIGNAL_ACCOUNT_POST_CREATE - this is triggered at the very end of Account.create(). Note that calling evennia.create.create_account (which is called internally by Account.create) will not trigger this signal. This is because using Account.create() is expected to be the most commonly used way for users to themselves create accounts during login. It passes and extra kwarg ip with the client IP of the connecting account. • SIGNAL_ACCOUNT_POST_LOGIN - this will always fire when the account has authenticated. Sends extra kwarg session with the new Session object involved. • SIGNAL_ACCCOUNT_POST_FIRST_LOGIN - this fires just before SIGNAL_ACCOUNT_POST_LOGIN but only if this is the first connection done (that is, if there are no previous sessions connected). Also passes the session along as a kwarg. • SIGNAL_ACCOUNT_POST_LOGIN_FAIL - sent when someone tried to log into an account by failed. Passes the session as an extra kwarg. • SIGNAL_ACCOUNT_POST_LOGOUT - always fires when an account logs off, no matter if other sessions re- main or not. Passes the disconnecting session along as a kwarg. • SIGNAL_ACCOUNT_POST_LAST_LOGOUT - fires before SIGNAL_ACCOUNT_POST_LOGOUT, but only if this is the last Session to disconnect for that account. Passes the session as a kwarg. • SIGNAL_OBJECT_POST_PUPPET - fires when an account puppets this object. Extra kwargs session and account represent the puppeting entities. SIGNAL_OBJECT_POST_UNPUPPET - fires when the sending object is unpuppeted. Extra kwargs are session and account. • SIGNAL_ACCOUNT_POST_RENAME - triggered by the setting of Account.username. Passes extra kwargs old_name, new_name. • SIGNAL_TYPED_OBJECT_POST_RENAME - triggered when any Typeclassed entity’s key is changed. Extra kwargs passed are old_key and new_key. • SIGNAL_SCRIPT_POST_CREATE - fires when a script is first created, after any hooks. • SIGNAL_CHANNEL_POST_CREATE - fires when a Channel is first created, after any hooks.

168 Chapter 11. Server Components Evennia Documentation, Release 0.9

• SIGNAL_HELPENTRY_POST_CREATE - fires when a help entry is first created.

The evennia.signals module also gives you conveneient access to the default Django signals (these use a different naming convention).

• pre_save - fired when any database entitiy’s .save method fires, before any saving has happened. • post_save - fires after saving a database entity. • pre_delete - fires just before a database entity is deleted. • post_delete - fires after a database entity was deleted. • pre_init - fires before a typeclass’ __init__ method (which in turn happens before the at_init hook fires). • post_init - triggers at the end of __init__ (still before the at_init hook).

These are highly specialized Django signals that are unlikely to be useful to most users. But they are included here for completeness.

• m2m_changed - fires after a Many-to-Many field (like db_attributes) changes. • pre_migrate - fires before database migration starts with evennia migrate. • post_migrate - fires after database migration finished. • request_started - sent when HTTP request begins. • request_finished - sent when HTTP request ends. • settings_changed - sent when changing settings due to @override_settings decorator (only rele- vant for unit testing) • template_rendered - sent when test system renders http template (only useful for unit tests). • connection_creation - sent when making initial connection to database.

11.16 Attributes

When performing actions in Evennia it is often important that you store data for later. If you write a menu system, you have to keep track of the current location in the menu tree so that the player can give correct subsequent commands. If you are writing a combat system, you might have a combattant’s next roll get easier dependent on if their opponent failed. Your characters will probably need to store roleplaying-attributes like strength and agility. And so on.

Typeclassed game entities (Accounts, Objects, Scripts and Channels) always have Attributes associated with them. Attributes are used to store any type of data ‘on’ such entities. This is different from storing data in properties already defined on entities (such as key or location) - these have very specific names and require very specific types of data (for example you couldn’t assign a python list to the key property no matter how hard you tried). Attributes come into play when you

11.16. Attributes 169 Evennia Documentation, Release 0.9

want to assign arbitrary data to arbitrary names.

11.16.1 The .db and .ndb shortcuts

To save persistent data on a Typeclassed object you normally use the db (DataBase) operator. Let’s try to save some data to a Rose (an Object):

# saving rose.db.has_thorns= True # getting it back is_ouch= rose.db.has_thorns

This looks like any normal Python assignment, but that db makes sure that an Attribute is created behind the scenes and is stored in the database. Your rose will continue to have thorns throughout the life of the server now, until you deliberately remove them.

To be sure to save non-persistently, i.e. to make sure NOT to create a database entry, you use ndb (NonDataBase). It works in the same way:

# saving rose.ndb.has_thorns= True # getting it back is_ouch= rose.ndb.has_thorns

Technically, ndb has nothing to do with Attributes, despite how similar they look. No Attribute object is created behind the scenes when using ndb. In fact the database is not invoked at all since we are not interested in persistence. There is however an important reason to use ndb to store data rather than to just store variables direct on entities - ndb-stored data is tracked by the server and will not be purged in various cache-cleanup operations Evennia may do while it runs. Data stored on ndb (as well as db) will also be easily listed by example the @examine command.

You can also del properties on db and ndb as normal. This will for example delete an Attribute:

del rose.db.has_thorns

Both db and ndb defaults to offering an all() method on themselves. This returns all associated attributes or non-persistent properties.

list_of_all_rose_attributes= rose.db.all() list_of_all_rose_ndb_attrs= rose.ndb.all()

170 Chapter 11. Server Components Evennia Documentation, Release 0.9

If you use all as the name of an attribute, this will be used instead. Later deleting your custom all will return the default behaviour.

11.16.2 The AttributeHandler

The .db and .ndb properties are very convenient but if you don’t know the name of the Attribute beforehand they cannot be used. Behind the scenes .db actually accesses the AttributeHandler which sits on typeclassed entities as the .attributes property. .ndb does the same for the .nattributes property.

The handlers have normal access methods that allow you to manage and retrieve Attributes and NAttributes:

• has('attrname') - this checks if the object has an Attribute with this key. This is equivalent to doing obj.db.attrname. • get(...) - this retrieves the given Attribute. Normally the value property of the Attribute is returned, but the method takes keywords for returning the Attribute object itself. By supplying an accessing_object to the call one can also make sure to check permissions before modifying anything. • add(...) - this adds a new Attribute to the object. An optional lockstring can be supplied here to restrict future access and also the call itself may be checked against locks. • remove(...) - Remove the given Attribute. This can optionally be made to check for permission before performing the deletion. - clear(...) - removes all Attributes from object. • all(...) - returns all Attributes (of the given category) attached to this object.

See this section for more about locking down Attribute access and editing. The Nattribute offers no concept of access control.

Some examples:

import evennia obj= evennia.search_object("MyObject")

obj.attributes.add("test","testvalue") print(obj.db.test) # prints "testvalue" print(obj.attributes.get("test")) #" print(obj.attributes.all()) # prints [] obj.attributes.remove("test")

11.16.3 Properties of Attributes

An Attribute object is stored in the database. It has the following properties: • key - the name of the Attribute. When doing e.g. obj.db.attrname = value, this property is set to attrname.

11.16. Attributes 171 Evennia Documentation, Release 0.9

• value - this is the value of the Attribute. This value can be anything which can be pickled - objects, lists, numbers or what have you (see this section for more info). In the example obj.db.attrname = value, the value is stored here. • category - this is an optional property that is set to None for most Attributes. Setting this allows to use Attributes for different functionality. This is usually not needed unless you want to use Attributes for very different functionality (Nicks is an example of using Attributes in this way). To modify this property you need to use the Attribute Handler. • strvalue - this is a separate value field that only accepts strings. This severely limits the data possible to store, but allows for easier database lookups. This property is usually not used except when re-using Attributes for some other purpose (Nicks use it). It is only accessible via the Attribute Handler. There are also two special properties: • attrtype - this is used internally by Evennia to separate Nicks, from Attributes (Nicks use Attributes behind the scenes). • model - this is a natural-key describing the model this Attribute is attached to. This is on the form app- name.modelclass, like objects.objectdb. It is used by the Attribute and NickHandler to quickly sort matches in the database. Neither this nor attrtype should normally need to be modified. Non-database attributes have no equivalence to category nor strvalue, attrtype or model.

11.16.4 Persistent vs non-persistent

So persistent data means that your data will survive a server reboot, whereas with non-persistent data it will not . . .

. . . So why would you ever want to use non-persistent data? The answer is, you don’t have to. Most of the time you really want to save as much as you possibly can. Non-persistent data is potentially useful in a few situations though.

• You are worried about database performance. Since Evennia caches Attributes very aggressively, this is not an issue unless you are reading and writing to your Attribute very often (like many times per second). Reading from an already cached Attribute is as fast as reading any Python property. But even then this is not likely something to worry about: Apart from Evennia’s own caching, modern database systems themselves also cache data very efficiently for speed. Our default database even runs completely in RAM if possible, alleviating much of the need to write to disk during heavy loads. • A more valid reason for using non-persistent data is if you want to lose your state when logging off. Maybe you are storing throw-away data that are re-initialized at server startup. Maybe you are implementing some caching of your own. Or maybe you are testing a buggy Script that does potentially harmful stuff to your character object. With non-persistent storage you can be sure that whatever is messed up, it’s nothing a server reboot can’t clear up. • NAttributes have no restrictions at all on what they can store (see next section), since they don’t need to worry about being saved to the database - they work very well for temporary storage. • You want to implement a fully or partly non-. Who are we to argue with your grand vision!

11.16.5 What types of data can I save in an Attribute?

None of the following affects NAttributes, which does not invoke the database at all. There are no restrictions to what can be stored in a NAttribute.

172 Chapter 11. Server Components Evennia Documentation, Release 0.9

The database doesn’t know anything about Python objects, so Evennia must serialize Attribute values into a string representation in order to store it to the database. This is done using the pickle module of Python (the only exception is if you use the strattr keyword of the AttributeHandler to save to the strvalue field of the Attribute. In that case you can only save strings which will not be pickled).

It’s important to note that when you access the data in an Attribute you are always de-serializing it from the database representation every time. This is because we allow for storing database-entities in Attributes too. If we cached it as its Python form, we might end up with situations where the database entity was deleted since we last accessed the Attribute. De-serializing data with a database-entity in it means querying the database for that object and making sure it still exists (otherwise it will be set to None). Performance-wise this is usually not a big deal. But if you are accessing the Attribute as part of some big loop or doing a large amount of reads/writes you should first extract it to a temporary variable, operate on that and then save the result back to the Attribute. If you are storing a more complex structure like a dict or a list you should make sure to “disconnect” it from the database before looping over it, as mentioned in the Retrieving Mutable Objects section below.

Storing single objects

With a single object, we mean anything that is not iterable, like numbers, strings or custom class instances without the __iter__ method. • You can generally store any non-iterable Python entity that can be pickled. • Single database objects/typeclasses can be stored as any other in the Attribute. These can normally not be pickled, but Evennia will behind the scenes convert them to an internal representation using their classname, database-id and creation-date with a microsecond precision, guaranteeing you get the same object back when you access the Attribute later. • If you hide a database object inside a non-iterable custom class (like stored as a variable inside it), Evennia will not know it’s there and won’t convert it safely. Storing classes with such hidden database objects is not supported and will lead to errors!

# Examples of valid single-value attribute data: obj.db.test1= 23 obj.db.test1= False # a database object (will be stored as an internal representation) obj.db.test2= myobj

# example of an invalid, "hidden" dbobject class Invalid(object): def __init__(self, dbobj): # no way for Evennia to know this is a dbobj self.dbobj= dbobj invalid= Invalid(myobj) obj.db.invalid= invalid # will cause error!

Storing multiple objects

This means storing objects in a collection of some kind and are examples of iterables, pickle-able

11.16. Attributes 173 Evennia Documentation, Release 0.9

entities you can loop over in a for-loop. Attribute-saving supports the following iterables:

• Tuples, like (1,2,"test", ). • Lists, like [1,2,"test", ]. • Dicts, like {1:2, "test":]. • Sets, like {1,2,"test",}. • collections.OrderedDict, like OrderedDict((1,2), ("test", )). • collections.Deque, like deque((1,2,"test",)). • Nestings of any combinations of the above, like lists in dicts or an OrderedDict of tuples, each containing dicts, etc. • All other iterables (i.e. entities with the __iter__ method) will be converted to a list. Since you can use any combination of the above iterables, this is generally not much of a limitation. Any entity listed in the Single object section above can be stored in the iterable. As mentioned in the previous section, database entities (aka typeclasses) are not possible to pickle. So when storing an iterable, Evennia must recursively traverse the iterable and all its nested sub-iterables in order to find eventual database objects to convert. This is a very fast process but for efficiency you may want to avoid too deeply nested structures if you can.

# examples of valid iterables to store obj.db.test3= [obj1, 45, obj2, 67] # a dictionary obj.db.test4={'str':34,'dex':56,'agi':22,'int':77} # a mixed dictionary/list obj.db.test5={'members': [obj1,obj2,obj3],'enemies':[obj4,obj5]} # a tuple with a list in it obj.db.test6=(1,3,4,8,["test","test2"],9) # a set obj.db.test7= set([1,2,3,4,5]) # in-situ manipulation obj.db.test8=[1,2,{"test":1}] obj.db.test8[0]=4 obj.db.test8[2]["test"]=5 # test8 is now [4,2,{"test":5}]

Retrieving Mutable objects

A side effect of the way Evennia stores Attributes is that mutable iterables (iterables that can be modified in-place after they were created, which is everything except tuples) are handled by custom objects called _SaverList, _SaverDict etc. These _Saver... classes behave just like the normal variant except that they are aware of the database and saves to it whenever new data gets assigned to them. This is what allows you to do things like self.db.mylist[7] = val and be sure that the new version of list is saved. Without this you would have to load the list into a temporary variable, change it and then re-assign it to the Attribute in order for it to save.

There is however an important thing to remember. If you retrieve your mutable iterable into another variable, e.g. mylist2 = obj.db.mylist, your new variable (mylist2) will still be a _SaverList. This means it will continue to save itself to the database whenever it is updated!

174 Chapter 11. Server Components Evennia Documentation, Release 0.9

obj.db.mylist=[1,2,3,4] mylist= obj.db.mylist mylist[3]=5 # this will also update database print(mylist) # this is now [1,2,3,5] print(obj.db.mylist) # this is also [1,2,3,5]

To “disconnect” your extracted mutable variable from the database you simply need to convert the _Saver... iterable to a normal Python structure. So to convert a _SaverList, you use the list() function, for a _SaverDict you use dict() and so on.

obj.db.mylist=[1,2,3,4] mylist= list(obj.db.mylist) # convert to normal list mylist[3]=5 print(mylist) # this is now [1,2,3,5] print(obj.db.mylist) # this is still [1,2,3,4]

A further problem comes with nested mutables, like a dict containing lists of dicts or something like that. Each of these nested mutables would be _Saver* structures connected to the database and disconnecting the outermost one of them would not disconnect those nested within. To make really sure you disonnect a nested structure entirely from the database, Evennia provides a special function evennia.utils.dbserialize.deserialize:

from evennia.utils.dbserialize import deserialize decoupled_mutables= deserialize(nested_mutables)

The result of this operation will be a structure only consisting of normal Python mutables (list instead of _SaverList and so on).

Remember, this is only valid for mutable iterables. Immutable objects (strings, numbers, tuples etc) are already disconnected from the database from the onset.

obj.db.mytup = (1,2,[3,4]) obj.db.mytup[0] = 5 # this fails since tuples are immutable

# this works but will NOT update database since outermost is a tuple obj.db.mytup[2][1] = 5 print(obj.db.mytup[2][1]) # this still returns 4, not 5

mytup1 = obj.db.mytup # mytup1 is already disconnected from database since

˓→outermost # iterable is a tuple, so we can edit the internal list as

˓→we want (continues on next page)

11.16. Attributes 175 Evennia Documentation, Release 0.9

(continued from previous page) # without affecting the database.

| Attributes will fetch data fresh from the database whenever you read them, so | if you are performing big operations on a mutable Attribute property (such as looping over a list | or dict) you should make sure to “disconnect” the Attribute’s value first and operate on this | rather than on the Attribute. You can gain dramatic speed improvements to big loops this | way.

11.16.6 Locking and checking Attributes

Attributes are normally not locked down by default, but you can easily change that for individual Attributes (like those that may be game-sensitive in games with user-level building).

First you need to set a lock string on your Attribute. Lock strings are specified Locks. The relevant lock types are • attrread - limits who may read the value of the Attribute • attredit - limits who may set/change this Attribute

You cannot use the db handler to modify Attribute object (such as setting a lock on them) - The db handler will return the Attribute’s value, not the Attribute object itself. Instead you use the AttributeHandler and set it to return the object instead of the value:

lockstring="attread:all();attredit:perm(Admins)" obj.attributes.get("myattr", return_obj=True).locks.add(lockstring)

Note the return_obj keyword which makes sure to return the Attribute object so its LockHandler could be accessed.

A lock is no good if nothing checks it – and by default Evennia does not check locks on Attributes. You have to add a check to your commands/code wherever it fits (such as before setting an Attribute).

# in some command code where we want to limit # setting of a given attribute name on an object attr= obj.attributes.get(attrname, return_obj=True, accessing_obj=caller, default=None, default_access=False) if not attr: (continues on next page)

176 Chapter 11. Server Components Evennia Documentation, Release 0.9

(continued from previous page) caller.msg("You cannot edit that Attribute!") return # edit the Attribute here

The same keywords are available to use with obj.attributes.set() and obj.attributes.remove(), those will check for the attredit lock type.

11.17 Nicks

Nicks, short for Nicknames is a system allowing an object (usually a Account) to assign custom replacement names for other game entities. Nicks are not to be confused with Aliases. Setting an Alias on a game entity actually changes an inherent attribute on that entity, and everyone in the game will be able to use that alias to address the entity thereafter. A Nick on the other hand, is used to map a different way you alone can refer to that entity. Nicks are also commonly used to replace your input text which means you can create your own aliases to default commands. Default Evennia use Nicks in three flavours that determine when Evennia actually tries to do the substitution. • inputline - replacement is attempted whenever you write anything on the command line. This is the default. • objects - replacement is only attempted when referring to an object • accounts - replacement is only attempted when referring an account Here’s how to use it in the default command set (using the nick command): nick ls= look

This is a good one for unix/linux users who are accustomed to using the ls command in their daily life. It is equivalent to nick/inputline ls = look. nick/object mycar2= The red sports car

With this example, substitutions will only be done specifically for commands expecting an object reference, such as look mycar2 becomes equivalent to “look The red sports car”. nick/accounts tom= Thomas Johnsson

This is useful for commands searching for accounts explicitly:

@find *tom

One can use nicks to speed up input. Below we add ourselves a quicker way to build red buttons. In the future just writing rb will be enough to execute that whole long string. nick rb= @create button:examples.red_button.RedButton

Nicks could also be used as the start for building a “recog” system suitable for an RP mud.

11.17. Nicks 177 Evennia Documentation, Release 0.9

nick/account Arnold= The mysterious hooded man

The nick replacer also supports unix-style templating:

nick build $1 $2 = @create/drop $1;$2

This will catch space separated arguments and store them in the the tags $1 and $2, to be inserted in the replacement string. This example allows you to do build box crate and have Evennia see @create/drop box;crate. You may use any $ numbers between 1 and 99, but the markers must match between the nick pattern and the replace- ment. If you want to catch “the rest” of a command argument, make sure to put a $ tag with no spaces to the right of it - it will then receive everything up until the end of the line. You can also use shell-type wildcards: • * - matches everything. • ? - matches a single character. • [seq] - matches everything in the sequence, e.g. [xyz] will match both x, y and z • [!seq] - matches everything not in the sequence. e.g. [!xyz] will match all but x,y z.

11.17.1 Coding with nicks

Nicks are stored as the Nick database model and are referred from the normal Evennia object through the nicks property - this is known as the NickHandler. The NickHandler offers effective error checking, searches and conversion.

# A command/channel nick: obj.nicks.add("greetjack","tell Jack = Hello pal!")

# An object nick: obj.nicks.add("rose","The red flower", nick_type="object")

# An account nick: obj.nicks.add("tom","Tommy Hill", nick_type="account")

# My own custom nick type (handled by my own game code somehow): obj.nicks.add("hood","The hooded man", nick_type="my_identsystem")

# get back the translated nick: full_name= obj.nicks.get("rose", nick_type="object")

# delete a previous set nick object.nicks.remove("rose", nick_type="object")

In a command definition you can reach the nick handler through self.caller.nicks. See the nick command in evennia/commands/default/general.py for more examples. As a last note, The Evennia channel alias systems are using nicks with the nick_type="channel" in order to allow users to create their own custom aliases to channels.

11.18 Advanced note

Internally, nicks are Attributes saved with the db_attrype set to “nick” (normal Attributes has this set to None).

178 Chapter 11. Server Components Evennia Documentation, Release 0.9

The nick stores the replacement data in the Attribute.db_value field as a tuple with four fields (regex_nick, template_string, raw_nick, raw_template). Here regex_nick is the converted regex representa- tion of the raw_nick and the template-string is a version of the raw_template prepared for efficient replacement of any $- type markers. The raw_nick and raw_template are basically the unchanged strings you enter to the nick command (with unparsed $ etc). If you need to access the tuple for some reason, here’s how:

tuple= obj.nicks.get("nickname", return_tuple= True) # or, alternatively tuple= obj.nicks.get("nickname", return_obj= True).value

11.19 Tags

A common task of a game designer is to organize and find groups of objects and do operations on them. A classic example is to have a weather script affect all “outside” rooms. Another would be for a player casting a magic spell that affects every location “in the dungeon”, but not those “outside”. Another would be to quickly find everyone joined with a particular guild or everyone currently dead. Tags are short text labels that you attach to objects so as to easily be able to retrieve and group them. An Evennia entity can be tagged with any number of Tags. On the database side, Tag entities are shared between all objects with that tag. This makes them very efficient but also fundamentally different from Attributes, each of which always belongs to one single object. In Evennia, Tags are technically also used to implement Aliases (alternative names for objects) and Permissions (simple strings for Locks to check for).

11.19.1 Properties of Tags (and Aliases and Permissions)

Tags are unique. This means that there is only ever one Tag object with a given key and category. When Tags are assigned to game entities, these entities are actually sharing the same Tag. This means that Tags are not suitable for storing information about a single object - use an Attribute for this instead. Tags are a lot more limited than Attributes but this also makes them very quick to lookup in the database - this is the whole point. Tags have the following properties, stored in the database: • key - the name of the Tag. This is the main property to search for when looking up a Tag. • category - this category allows for retrieving only specific subsets of tags used for different purposes. You could have one category of tags for “zones”, another for “outdoor locations”, for example. • data - this is an optional text field with information about the tag. Remember that Tags are shared between entities, so this field cannot hold any object-specific information. Usually it would be used to hold info about the group of entities the Tag is tagging - possibly used for contextual help like a tool tip. It is not used by default. There are also two special properties. These should usually not need to be changed or set, it is used internally by Evennia to implement various other uses it makes of the Tag object: • model - this holds a natural-key description of the model object that this tag deals with, on the form applica- tion.modelclass, for example objects.objectdb. It used by the TagHandler of each entity type for correctly storing the data behind the scenes. • tagtype - this is a “top-level category” of sorts for the inbuilt children of Tags, namely Aliases and Permissions. The Taghandlers using this special field are especially intended to free up the category property for any use you desire.

11.19. Tags 179 Evennia Documentation, Release 0.9

11.19.2 Adding/Removing Tags

You can tag any typeclassed object, namely Objects, Accounts, Scripts and Channels. General tags are added by the Taghandler. The tag handler is accessed as a property tags on the relevant entity:

mychair.tags.add("furniture") myroom.tags.add("dungeon#01") myscript.tags.add("weather", category="climate") myaccount.tags.add("guestaccount")

mychair.tags.all() # returns a list of Tags mychair.tags.remove("furniture") mychair.tags.clear()

Adding a new tag will either create a new Tag or re-use an already existing one. When using remove, the Tag is not deleted but are just disconnected from the tagged object. This makes for very quick operations. The clear method removes (disconnects) all Tags from the object. You can also use the default @tag command:

@tag mychair= furniture

11.19.3 Searching for objects with a given tag

Usually tags are used as a quick way to find tagged database entities. You can retrieve all objects with a given Tag like this in code:

import evennia

# all methods return Querysets

# search for objects objs= evennia.search_tag("furniture") dungeon= evennia.search_tag("dungeon#01") forest_rooms= evennia.search_tag(category="forest") forest_meadows= evennia.search_tag("meadow", category="forest") magic_meadows= evennia.search_tag("meadow", category="magical")

# search for scripts weather= evennia.search_tag_script("weather") climates= evennia.search_tag_script(category="climate")

# search for accounts accounts= evennia.search_tag_account("guestaccount")

Using any of the search_tag variants will all return Django Querysets, including if you only have one match. You can treat querysets as lists and iterate over them, or continue building search queries with them. Remember when searching that not setting a category means setting it to None - this does not mean that category is undefined, rather None is considered the default, unnamed category.

import evennia

myobj1.tags.add("foo") # implies category=None myobj2.tags.add("foo", category="bar")

# this returns a queryset with *only* myobj1 objs= evennia.search_tag("foo") (continues on next page)

180 Chapter 11. Server Components Evennia Documentation, Release 0.9

(continued from previous page)

# these return a queryset with *only* myobj2 objs= evennia.search_tag("foo", category="bar") # or objs= evennia.search_tag(category="bar")

There is also an in-game command that deals with assigning and using (Object-) tags:

@tag/search furniture

11.19.4 Using Aliases and Permissions

Aliases and Permissions are implemented using normal TagHandlers that simply save Tags with a different tagtype. These handlers are named aliases and permissions on all Objects. They are used in the same way as Tags above:

boy.aliases.add("rascal") boy.permissions.add("Builders") boy.permissions.remove("Builders")

all_aliases= boy.aliases.all()

and so on. Similarly to how @tag works in-game, there is also the @perm command for assigning permissions and @alias command for aliases.

11.19.5 Assorted notes

Generally, tags are enough on their own for grouping objects. Having no tag category is perfectly fine and the normal operation. Simply adding a new Tag for grouping objects is often better than making a new category. So think hard before deciding you really need to categorize your Tags.

That said, tag categories can be useful if you build some game system that uses tags. You can then use tag categories to make sure to separate tags created with this system from any other tags created elsewhere. You can then supply custom search methods that only find objects tagged with tags of that category. An example of this is found in the Zone tutorial.

11.20 Locks

For most games it is a good idea to restrict what people can do. In Evennia such restrictions are applied and checked by something called locks. All Evennia entities (Commands, Objects, Scripts, Accounts, Help System, messages and channels) are accessed through locks. A lock can be thought of as an “access rule” restricting a particular use of an Evennia entity. Whenever another entity wants that kind of access the lock will analyze that entity in different ways to determine if access should be granted or not. Evennia implements a “lockdown” philosophy - all entities are inaccessible unless you explicitly define a lock that allows some or full access. Let’s take an example: An object has a lock on itself that restricts how people may “delete” that object. Apart from knowing that it restricts deletion, the lock also knows that only players with the specific ID of, say, 34 are allowed to delete it. So whenever a player tries to run delete on the object, the delete command makes sure to check if this

11.20. Locks 181 Evennia Documentation, Release 0.9

player is really allowed to do so. It calls the lock, which in turn checks if the player’s id is 34. Only then will it allow delete to go on with its job.

11.20.1 Setting and checking a lock

The in-game command for setting locks on objects is lock:

> lock obj=

The is a string of a certain form that defines the behaviour of the lock. We will go into more detail on how should look in the next section. Code-wise, Evennia handles locks through what is usually called locks on all relevant entities. This is a handler that allows you to add, delete and check locks.

myobj.locks.add()

One can call locks.check() to perform a lock check, but to hide the underlying implementation all ob- jects also have a convenience function called access. This should preferably be used. In the example below, accessing_obj is the object requesting the ‘delete’ access whereas obj is the object that might get deleted. This is how it would look (and does look) from inside the delete command:

if not obj.access(accessing_obj,'delete'): accessing_obj.msg("Sorry, you may not delete that.") return

11.20.2 Defining locks

Defining a lock (i.e. an access restriction) in Evennia is done by adding simple strings of lock definitions to the object’s locks property using obj.locks.add(). Here are some examples of lock strings (not including the quotes):

delete:id(34) # only allow obj #34 to delete edit:all() # let everyone edit # only those who are not "very_weak" or are Admins may pick this up get: not attr(very_weak) or perm(Admin)

Formally, a lockstring has the following syntax:

access_type: [NOT] lockfunc1([arg1,..]) [AND|OR] [NOT] lockfunc2([arg1,...]) [...]

where [] marks optional parts. AND, OR and NOT are not case sensitive and excess spaces are ignored. lockfunc1, lockfunc2 etc are special lock functions available to the lock system. So, a lockstring consists of the type of restriction (the access_type), a (:) and then an expression involving function calls that determine what is needed to pass the lock. Each function returns either True or False. AND, OR and NOT work as they do normally in Python. If the total result is True, the lock is passed. You can create several lock types one after the other by separating them with a (;) in the lockstring. The string below yields the same result as the previous example:

delete:id(34);edit:all();get: not attr(very_weak) or perm(Admin)

182 Chapter 11. Server Components Evennia Documentation, Release 0.9

Valid access_types

An access_type, the first part of a lockstring, defines what kind of capability a lock controls, such as “delete” or “edit”. You may in principle name your access_type anything as long as it is unique for the particular object. The name of the access types is not case-sensitive. If you want to make sure the lock is used however, you should pick access_type names that you (or the default command set) actually checks for, as in the example of delete above that uses the ‘delete’ access_type. Below are the access_types checked by the default commandset. • Commands • cmd - this defines who may call this command at all. • Objects: • control - who is the “owner” of the object. Can set locks, delete it etc. Defaults to the creator of the object. • call - who may call Object-commands stored on this Object except for the Object itself. By default, Objects share their Commands with anyone in the same location (e.g. so you can ‘press’ a Button object in the room). For Characters and Mobs (who likely only use those Commands for themselves and don’t want to share them) this should usually be turned off completely, using something like call:false(). • examine - who may examine this object’s properties. • delete - who may delete the object. • edit - who may edit properties and attributes of the object. • view - if the look command will display/list this object • get- who may pick up the object and carry it around. • puppet - who may “become” this object and control it as their “character”. • attrcreate - who may create new attributes on the object (default True) • Characters: • Same as for Objects • Exits: • Same as for Objects • traverse - who may pass the exit. • Accounts: • examine - who may examine the account’s properties. • delete - who may delete the account. • edit - who may edit the account’s attributes and properties. • msg - who may send messages to the account. • boot - who may boot the account. • Attributes: (only checked by obj.secure_attr) • attrread - see/access attribute • attredit - change/delete attribute • Channels: • control - who is administrating the channel. This means the ability to delete the channel, boot listeners etc.

11.20. Locks 183 Evennia Documentation, Release 0.9

• send - who may send to the channel. • listen - who may subscribe and listen to the channel. • HelpEntry: • examine - who may view this help entry (usually everyone) • edit - who may edit this help entry. So to take an example, whenever an exit is to be traversed, a lock of the type traverse will be checked. Defining a suitable lock type for an exit object would thus involve a lockstring traverse: .

Custom access_types

As stated above, the access_type part of the lock is simply the ‘name’ or ‘type’ of the lock. The text is an arbitrary string that must be unique for an object. If adding a lock with the same access_type as one that already exists on the object, the new one override the old one. For example, if you wanted to create a bulletin board system and wanted to restrict who can either read a board or post to a board. You could then define locks such as:

obj.locks.add("read:perm(Player);post:perm(Admin)")

This will create a ‘read’ access type for Characters having the Player permission or above and a ‘post’ access type for those with Admin permissions or above (see below how the perm() lock function works). When it comes time to test these permissions, simply check like this (in this example, the obj may be a board on the bulletin board system and accessing_obj is the player trying to read the board):

if not obj.access(accessing_obj,'read'): accessing_obj.msg("Sorry, you may not read that.") return

Lock functions

A lock function is a normal Python function put in a place Evennia looks for such functions. The modules Evennia looks at is the list settings.LOCK_FUNC_MODULES. All functions in any of those modules will automatically be considered a valid lock function. The default ones are found in evennia/locks/lockfuncs.py and you can start adding your own in mygame/server/conf/lockfuncs.py. You can append the setting to add more module paths. To replace a default lock function, just add your own with the same name. A lock function must always accept at least two arguments - the accessing object (this is the object wanting to get access) and the accessed object (this is the object with the lock). Those two are fed automatically as the first two arguments to the function when the lock is checked. Any arguments explicitly given in the lock definition will appear as extra arguments.

# A simple example lock function. Called with e.g. `id(34)`. This is # defined in, say mygame/server/conf/lockfuncs.py

def id(accessing_obj, accessed_obj, *args, **kwargs): if args: wanted_id= args[0] return accessing_obj.id == wanted_id return False

The above could for example be used in a lock function like this:

184 Chapter 11. Server Components Evennia Documentation, Release 0.9

# we have `obj` and `owner_object` from before obj.locks.add("edit: id(%i)"% owner_object.id)

We could check if the “edit” lock is passed with something like this:

# as part of a Command's func() method, for example if not obj.access(caller,"edit"): caller.msg("You don't have access to edit this!") return

In this example, everyone except the caller with the right id will get the error. (Using the * and ** syntax causes Python to magically put all extra arguments into a list args and all keyword arguments into a dictionary kwargs respectively. If you are unfamiliar with how *args and **kwargs work, see the Python manuals). Some useful default lockfuncs (see src/locks/lockfuncs.py for more): • true()/all() - give access to everyone • false()/none()/superuser() - give access to none. Superusers bypass the check entirely and are thus the only ones who will pass this check. • perm(perm) - this tries to match a given permission property, on an Account firsthand, on a Character second. See below. • perm_above(perm) - like perm but requires a “higher” permission level than the one given. • id(num)/dbref(num) - checks so the access_object has a certain dbref/id. • attr(attrname) - checks if a certain Attribute exists on accessing_object. • attr(attrname, value) - checks so an attribute exists on accessing_object and has the given value. • attr_gt(attrname, value) - checks so accessing_object has a value larger (>) than the given value. • attr_ge, attr_lt, attr_le, attr_ne - corresponding for >=, <, <= and !=. • holds(objid) - checks so the accessing objects contains an object of given name or dbref. • inside() - checks so the accessing object is inside the accessed object (the inverse of holds()). • pperm(perm), pid(num)/pdbref(num) - same as perm, id/dbref but always looks for permissions and dbrefs of Accounts, not on Characters. • serversetting(settingname, value) - Only returns True if Evennia has a given setting or a setting set to a given value.

11.20.3 Checking simple strings

Sometimes you don’t really need to look up a certain lock, you just want to check a lockstring. A common use is inside Commands, in order to check if a user has a certain permission. The lockhandler has a method check_lockstring(accessing_obj, lockstring, bypass_superuser=False) that allows this.

# inside command definition if not self.caller.locks.check_lockstring(self.caller,"dummy:perm(Admin)"): self.caller.msg("You must be an Admin or higher to do this!") return

Note here that the access_type can be left to a dummy value since this method does not actually do a Lock lookup.

11.20. Locks 185 Evennia Documentation, Release 0.9

11.20.4 Default locks

Evennia sets up a few basic locks on all new objects and accounts (if we didn’t, noone would have any access to anything from the start). This is all defined in the root Typeclasses of the respective entity, in the hook method basetype_setup() (which you usually don’t want to edit unless you want to change how basic stuff like rooms and exits store their internal variables). This is called once, before at_object_creation, so just put them in the latter method on your child object to change the default. Also creation commands like create changes the locks of objects you create - for example it sets the control lock_type so as to allow you, its creator, to control and delete the object.

11.21 Permissions

This section covers the underlying code use of permissions. If you just want to learn how to practically assign permissions in-game, refer to the Building Permissions page, which details how you use the perm command. A permission is simply a list of text strings stored in the handler permissions on Objects and Accounts. Permissions can be used as a convenient way to structure access levels and hierarchies. It is set by the perm command. Permissions are especially handled by the perm() and pperm() lock functions listed above. Let’s say we have a red_key object. We also have red chests that we want to unlock with this key. perm red_key= unlocks_red_chests

This gives the red_key object the permission “unlocks_red_chests”. Next we lock our red chests: lock red chest= unlock:perm(unlocks_red_chests)

What this lock will expect is to the fed the actual key object. The perm() lock function will check the permissions set on the key and only return true if the permission is the one given. Finally we need to actually check this lock somehow. Let’s say the chest has an command open sitting on itself. Somewhere in its code the command needs to figure out which key you are using and test if this key has the correct permission:

# self.obj is the chest # and used_key is the key we used as argument to # the command. The self.caller is the one trying # to unlock the chest if not self.obj.access(used_key,"unlock"): self.caller.msg("The key does not fit!") return

All new accounts are given a default set of permissions defined by settings. PERMISSION_ACCOUNT_DEFAULT. Selected permission strings can be organized in a permission hierarchy by editing the tuple settings. PERMISSION_HIERARCHY. Evennia’s default permission hierarchy is as follows:

Developer # like superuser but affected by locks Admin # can administrate accounts Builder # can edit the world Helper # can edit help files Player # can chat and send tells (default level)

(Also the plural form works, so you could use Developers etc too).

186 Chapter 11. Server Components Evennia Documentation, Release 0.9

There is also a Guest level below Player that is only active if settings.GUEST_ENABLED is set. This is never part of settings.PERMISSION_HIERARCHY. The main use of this is that if you use the lock function perm() mentioned above, a lock check for a particular permission in the hierarchy will also grant access to those with higher hierarchy access. So if you have the permission “Admin” you will also pass a lock defined as perm(Builder) or any of those levels below “Admin”. When doing an access check from an Object or Character, the perm() lock function will always first use the per- missions of any Account connected to that Object before checking for permissions on the Object. In the case of hierarchical permissions (Admins, Builders etc), the Account permission will always be used (this stops an Account from escalating their permission by puppeting a high-level Character). If the permission looked for is not in the hier- archy, an exact match is required, first on the Account and if not found there (or if no Account is connected), then on the Object itself. Here is how you use perm to give an account more permissions:

perm/account Tommy= Builders perm/account/del Tommy= Builders # remove it again

Note the use of the /account switch. It means you assign the permission to the Accounts Tommy instead of any Character that also happens to be named “Tommy”. Putting permissions on the Account guarantees that they are kept, regardless of which Character they are currently puppeting. This is especially important to remember when assigning permissions from the hierarchy tree - as men- tioned above, an Account’s permissions will overrule that of its character. So to be sure to avoid confusion you should generally put hierarchy permissions on the Account, not on their Characters (but see also quelling). Below is an example of an object without any connected account

obj1.permissions=["Builders","cool_guy"] obj2.locks.add("enter:perm_above(Accounts) and perm(cool_guy)")

obj2.access(obj1,"enter") # this returns True!

And one example of a puppet with a connected account:

account.permissions.add("Accounts") puppet.permissions.add("Builders","cool_guy") obj2.locks.add("enter:perm_above(Accounts) and perm(cool_guy)")

obj2.access(puppet,"enter") # this returns False!

11.21.1 Superusers

There is normally only one superuser account and that is the one first created when starting Evennia (User #1). This is sometimes known as the “Owner” or “God” user. A superuser has more than full access - it completely bypasses all locks so no checks are even run. This allows for the superuser to always have access to everything in an emergency. But it also hides any eventual errors you might have made in your lock definitions. So when trying out game systems you should either use quelling (see below) or make a second Developer-level character so your locks get tested correctly.

11.21.2 Quelling

The quell command can be used to enforce the perm() lockfunc to ignore permissions on the Account and instead use the permissions on the Character only. This can be used e.g. by staff to test out things with a lower permission level. Return to the normal operation with unquell. Note that quelling will use the smallest of any hierarchical

11.21. Permissions 187 Evennia Documentation, Release 0.9

permission on the Account or Character, so one cannot escalate one’s Account permission by quelling to a high- permission Character. Also the superuser can quell their powers this way, making them affectable by locks.

11.21.3 More Lock definition examples

examine: attr(eyesight, excellent) or perm(Builders)

You are only allowed to do examine on this object if you have ‘excellent’ eyesight (that is, has an Attribute eyesight with the value excellent defined on yourself) or if you have the “Builders” permission string assigned to you.

open: holds('the green key') or perm(Builder)

This could be called by the open command on a “door” object. The check is passed if you are a Builder or has the right key in your inventory.

cmd: perm(Builders)

Evennia’s command handler looks for a lock of type cmd to determine if a user is allowed to even call upon a particular command or not. When you define a command, this is the kind of lock you must set. See the default command set for lots of examples. If a character/account don’t pass the cmd lock type the command will not even appear in their help list.

cmd: not perm(no_tell)

“Permissions” can also be used to block users or implement highly specific bans. The above example would be be added as a lock string to the tell command. This will allow everyone not having the “permission” no_tell to use the tell command. You could easily give an account the “permission” no_tell to disable their use of this particular command henceforth.

dbref= caller.id lockstring="control:id( %s);examine:perm(Builders);delete:id(%s) or perm(Admin);

˓→get:all()"% (dbref, dbref) new_obj.locks.add(lockstring)

This is how the create command sets up new objects. In sequence, this permission string sets the owner of this object be the creator (the one running create). Builders may examine the object whereas only Admins and the creator may delete it. Everyone can pick it up.

11.21.4 A complete example of setting locks on an object

Assume we have two objects - one is ourselves (not superuser) and the other is an Object called box.

> create/drop box > desc box="This is a very big and heavy box."

We want to limit which objects can pick up this heavy box. Let’s say that to do that we require the would-be lifter to to have an attribute strength on themselves, with a value greater than 50. We assign it to ourselves to begin with.

> set self/strength= 45

Ok, so for testing we made ourselves strong, but not strong enough. Now we need to look at what happens when someone tries to pick up the the box - they use the get command (in the default set). This is defined in evennia/ commands/default/general.py. In its code we find this snippet:

188 Chapter 11. Server Components Evennia Documentation, Release 0.9

if not obj.access(caller,'get'): if obj.db.get_err_msg: caller.msg(obj.db.get_err_msg) else: caller.msg("You can't get that.") return

So the get command looks for a lock with the type get (not so surprising). It also looks for an Attribute on the checked object called get_err_msg in order to return a customized error message. Sounds good! Let’s start by setting that on the box:

> set box/get_err_msg= You are not strong enough to lift this box.

Next we need to craft a Lock of type get on our box. We want it to only be passed if the accessing object has the attribute strength of the right value. For this we would need to create a lock function that checks if attributes have a value greater than a given value. Luckily there is already such a one included in evennia (see evennia/locks/ lockfuncs.py), called attr_gt. So the lock string will look like this: get:attr_gt(strength, 50). We put this on the box now:

lock box= get:attr_gt(strength, 50)

Try to get the object and you should get the message that we are not strong enough. Increase your strength above 50 however and you’ll pick it up no problem. Done! A very heavy box! If you wanted to set this up in python code, it would look something like this:

from evennia import create_object

# create, then set the lock box= create_object( None, key="box") box.locks.add("get:attr_gt(strength, 50)")

# or we can assign locks in one go right away box= create_object( None, key="box", locks="get:attr_gt(strength, 50)")

# set the attributes box.db.desc="This is a very big and heavy box." box.db.get_err_msg="You are not strong enough to lift this box."

# one heavy box, ready to withstand all but the strongest...

11.21.5 On Django’s permission system

Django also implements a comprehensive permission/security system of its own. The reason we don’t use that is because it is app-centric (app in the Django sense). Its permission strings are of the form appname.permstring and it automatically adds three of them for each database model in the app - for the app evennia/object this would be for example ‘object.create’, ‘object.admin’ and ‘object.edit’. This makes a lot of sense for a web application, not so much for a MUD, especially when we try to hide away as much of the underlying architecture as possible. The django permissions are not completely gone however. We use it for validating passwords during login. It is also used exclusively for managing Evennia’s web-based admin site, which is a graphical front-end for the database of Evennia. You edit and assign such permissions directly from the web interface. It’s stand-alone from the permissions described above.

11.21. Permissions 189 Evennia Documentation, Release 0.9

11.22 Help System

An important part of Evennia is the online help system. This allows the players and staff alike to learn how to use the game’s commands as well as other information pertinent to the game. The help system has many different aspects, from the normal editing of help entries from inside the game, to auto-generated help entries during code development using the auto-help system.

11.22.1 Viewing the help database

The main command is help: help [searchstring]

This will show a list of help entries, ordered after categories. You will find two sections, Command help entries and Other help entries (initially you will only have the first one). You can use help to get more info about an entry; you can also give partial matches to get suggestions. If you give category names you will only be shown the topics in that category.

11.22.2 Command Auto-help system

A common item that requires help entries are in-game commands. Keeping these entries up-to-date with the actual source code functionality can be a chore. Evennia’s commands are therefore auto-documenting straight from the sources through its auto-help system. Only commands that you and your character can actually currently use are picked up by the auto-help system. That means an admin will see a considerably larger amount of help topics than a normal player when using the default help command. The auto-help system uses the __doc__ strings of your command classes and formats this to a nice-looking help entry. This makes for a very easy way to keep the help updated - just document your commands well and updating the help file is just a @reload away. There is no need to manually create and maintain help database entries for commands; as long as you keep the docstrings updated your help will be dynamically updated for you as well. Example (from a module with command definitions): class CmdMyCmd(Command): """ mycmd - my very own command

Usage: mycmd[/switches]

Switches: test - test the command run - do something else

This is my own command that does this and that.

""" # [...]

help_category="General" # default auto_help= True # default

# [...]

190 Chapter 11. Server Components Evennia Documentation, Release 0.9

The text at the very top of the command class definition is the class’ __doc__-string and will be shown to users looking for help. Try to use a consistent format - all default commands are using the structure shown above. You should also supply the help_category class property if you can; this helps to group help entries together for people to more easily find them. See the help command in-game to see the default categories. If you don’t specify the category, “General” is assumed. If you don’t want your command to be picked up by the auto-help system at all (like if you want to write its docs manually using the info in the next section or you use a cmdset that has its own help functionality) you can explicitly set auto_help class property to False in your command definition. Alternatively, you can keep the advantages of auto-help in commands, but control the display of command helps. You can do so by overriding the command’s get_help() method. By default, this method will return the class docstring. You could modify it to add custom behavior: the text returned by this method will be displayed to the character asking for help in this command.

11.22.3 Database help entries

These are all help entries not involving commands (this is handled automatically by the Command Auto-help system). Non-automatic help entries describe how your particular game is played - its rules, world descriptions and so on. A help entry consists of four parts: • The topic. This is the name of the help entry. This is what players search for when they are looking for help. The topic can contain spaces and also partial matches will be found. • The help category. Examples are Administration, Building, Comms or General. This is an overall grouping of similar help topics, used by the engine to give a better overview. • The text - the help text itself, of any length. • locks - a lock definition. This can be used to limit access to this help entry, maybe because it’s staff-only or otherwise meant to be restricted. Help commands check for access_types view and edit. An example of a lock string would be view:perm(Builders). You can create new help entries in code by using evennia.create_help_entry(). from evennia import create_help_entry entry= create_help_entry("emote", "Emoting is important because ...", category="Roleplaying", locks="view:all()")

From inside the game those with the right permissions can use the @sethelp command to add and modify help entries.

> @sethelp/add emote= The emote command is ...

Using @sethelp you can add, delete and append text to existing entries. By default new entries will go in the General help category. You can change this using a different form of the @sethelp command:

> @sethelp/add emote, Roleplaying= Emoting is important because...

If the category Roleplaying did not already exist, it is created and will appear in the help index. You can, finally, define a lock for the help entry by following the category with a lock definition:

> @sethelp/add emote, Roleplaying, view:all()= Emoting is ...

11.22. Help System 191 Evennia Documentation, Release 0.9

11.23 TickerHandler

One way to implement a dynamic MUD is by using “tickers”, also known as “heartbeats”. A ticker is a timer that fires (“ticks”) at a given interval. The tick triggers updates in various game systems.

11.23.1 About Tickers

Tickers are very common or even unavoidable in other mud code bases. Certain code bases are even hard-coded to rely on the concept of the global ‘tick’. Evennia has no such notion - the decision to use tickers is very much up to the need of your game and which requirements you have. The “ticker recipe” is just one way of cranking the wheels. The most fine-grained way to manage the flow of time is of course to use Scripts. Many types of operations (weather being the classic example) are however done on multiple objects in the same way at regular intervals, and for this, storing separate Scripts on each object is inefficient. The way to do this is to use a ticker with a “subscription model” - let objects sign up to be triggered at the same interval, unsubscribing when the updating is no longer desired. Evennia offers an optimized implementation of the subscription model - the TickerHandler. This is a singleton global handler reachable from evennia.TICKER_HANDLER. You can assign any callable (a function or, more commonly, a method on a database object) to this handler. The TickerHandler will then call this callable at an interval you specify, and with the arguments you supply when adding it. This continues until the callable un-subscribes from the ticker. The handler survives a reboot and is highly optimized in resource usage. Here is an example of importing TICKER_HANDLER and using it:

# we assume that obj has a hook "at_tick" defined on itself from evennia import TICKER_HANDLER as tickerhandler

tickerhandler.add(20, obj.at_tick)

That’s it - from now on, obj.at_tick() will be called every 20 seconds. You can also import function and tick that:

from evennia import TICKER_HANDLER as tickerhandler from mymodule import myfunc

tickerhandler.add(30, myfunc)

Removing (stopping) the ticker works as expected:

tickerhandler.remove(20, obj.at_tick) tickerhandler.remove(30, myfunc)

Note that you have to also supply interval to identify which subscription to remove. This is because the Ticker- Handler maintains a pool of tickers and a given callable can subscribe to be ticked at any number of different intervals. The full definition of the tickerhandler.add method is

tickerhandler.add(interval, callback, idstring="", persistent=True, *args, **kwargs)

Here *args and **kwargs will be passed to callback every interval seconds. If persistent is False, this subscription will not survive a server reload. Tickers are identified and stored by making a key of the callable itself, the ticker-interval, the persistent flag and the idstring (the latter being an empty string when not given explicitly).

192 Chapter 11. Server Components Evennia Documentation, Release 0.9

Since the arguments are not included in the ticker’s identification, the idstring must be used to have a specific callback triggered multiple times on the same interval but with different arguments:

tickerhandler.add(10, obj.update, "ticker1", True, 1, 2, 3) tickerhandler.add(10, obj.update, "ticker2", True, 4, 5)

Note that, when we want to send arguments to our callback within a ticker handler, we need to specify ``idstring`` and ``persistent`` before, unless we call our arguments as keywords, which would often be more readable: tickerhandler.add(10, obj.update, caller=self, value=118)

If you add a ticker with exactly the same combination of callback, interval and idstring, it will overload the existing ticker. This identification is also crucial for later removing (stopping) the subscription: tickerhandler.remove(10, obj.update, idstring="ticker1") tickerhandler.remove(10, obj.update, idstring="ticker2")

The callable can be on any form as long as it accepts the arguments you give to send to it in TickerHandler. add. Note that everything you supply to the TickerHandler will need to be pickled at some point to be saved into the database. Most of the time the handler will correctly store things like database objects, but the same restrictions as for Attributes apply to what the TickerHandler may store. When testing, you can stop all tickers in the entire game with tickerhandler.clear(). You can also view the currently subscribed objects with tickerhandler.all(). See the Weather Tutorial for an example of using the TickerHandler.

When not to use TickerHandler

Using the TickerHandler may sound very useful but it is important to consider when not to use it. Even if you are used to habitually relying on tickers for everything in other code bases, stop and think about what you really need it for. This is the main point: You should never use a ticker to catch changes. Think about it - you might have to run the ticker every second to react to the change fast enough. Most likely nothing will have changed at a given moment. So you are doing pointless calls (since skipping the call gives the same result as doing it). Making sure nothing’s changed might even be computationally expensive depending on the complexity of your system. Not to mention that you might need to run the check on every object in the database. Every second. Just to maintain status quo . . . Rather than checking over and over on the off-chance that something changed, consider a more proactive approach. Could you implement your rarely changing system to itself report when its status changes? It’s almost always much cheaper/efficient if you can do things “on demand”. Evennia itself uses hook methods for this very reason. So, if you consider a ticker that will fire very often but which you expect to have no effect 99% of the time, consider handling things things some other way. A self-reporting on-demand solution is usually cheaper also for fast-updating properties. Also remember that some things may not need to be updated until someone actually is examining or using them - any interim changes happening up to that moment are pointless waste of computing time. The main reason for needing a ticker is when you want things to happen to multiple objects at the same time without input from something else.

11.23. TickerHandler 193 Evennia Documentation, Release 0.9

11.24 MonitorHandler

The MonitorHandler is a system for watching changes in properties or Attributes on objects. A monitor can be thought of as a sort of trigger that responds to change. The main use for the MonitorHandler is to report changes to the client; for example the client Session may ask Evennia to monitor the value of the Characer’s health attribute and report whenever it changes. This way the client could for example update its health bar graphic as needed.

11.24.1 Using the MonitorHandler

The MontorHandler is accessed from the singleton evennia.MONITOR_HANDLER. The code for the handler is in evennia.scripts.monitorhandler. Here’s how to add a new monitor:

from evennia import MONITOR_HANDLER

MONITOR_HANDLER.add(obj, fieldname, callback, idstring="", persistent=False, **kwargs)

• obj (Typeclassed entity) - the object to monitor. Since this must be typeclassed, it means you can’t monitor changes on Sessions with the monitorhandler, for example. • fieldname (str) - the name of a field or Attribute on obj. If you want to monitor a database field you must specify its full name, including the starting db_ (like db_key, db_location etc). Any names not starting with db_ are instead assumed to be the names of Attributes. This difference matters, since the MonitorHandler will automatically know to watch the db_value field of the Attribute. • callback(callable) - This will be called as callback(fieldname=fieldname, obj=obj, **kwargs) when the field updates. • idstring (str) - this is used to separate multiple monitors on the same object and fieldname. This is required in order to properly identify and remove the monitor later. It’s also used for saving it. • persistent (bool) - if True, the monitor will survive a server reboot. Example:

from evennia import MONITOR_HANDLER as monitorhandler

def _monitor_callback(fieldname="", obj=None, **kwargs): # reporting callback that works both # for db-fields and Attributes if fieldname.startswith("db_"): new_value= getattr(obj, fieldname) else: # an attribute new_value= obj.attributes.get(fieldname)

obj.msg("%s.%s changed to' %s'."%\ (obj.key, fieldname, new_value))

# (we could add _some_other_monitor_callback here too)

# monitor Attribute (assume we have obj from before) monitorhandler.add(obj,"desc", _monitor_callback)

(continues on next page)

194 Chapter 11. Server Components Evennia Documentation, Release 0.9

(continued from previous page) # monitor same db-field with two different callbacks (must separate by id_string) monitorhandler.add(obj,"db_key", _monitor_callback, id_string="foo") monitorhandler.add(obj,"db_key", _some_other_monitor_callback, id_string="bar")

A monitor is uniquely identified by the combination of the object instance it is monitoring, the name of the field/attribute to monitor on that object and its idstring (obj + fieldname + idstring). The idstring will be the empty string unless given explicitly. So to “un-monitor” the above you need to supply enough information for the system to uniquely find the monitor to remove:

monitorhandler.remove(obj,"desc") monitorhandler.remove(obj,"db_key", idstring="foo") monitorhandler.remove(obj,"db_key", idstring="bar")

11.25 EvMenu

11.25.1 Introduction

The EvMenu utility class is located in evennia/utils/evmenu.py. It allows for easily adding interactive menus to the game; for example to implement Character creation, building commands or similar. Below is an example of offering NPC conversation choices:

The guard looks at you suspiciously. "No one is supposed to be in here ..." he says, a hand on his weapon. ______1. Try to bribe him [Cha+ 10 gold] 2. Convince him you work here [Int] 3. Appeal to his vanity [Cha] 4. Try to knock him out [Luck+ Dex] 5. Try to run away [Dex]

This is an example of a menu node. Think of a node as a point where the menu stops printing text and waits for user to give some input. By jumping to different nodes depending on the input, a menu is constructed.

To create the menu, EvMenu uses normal Python functions, one per node. It will load all those functions/nodes either from a module or by being passed a dictionary mapping the node’s names to said functions, like {"nodename": , ...}

11.25.2 Launching the menu

Initializing the menu is done using a call to the evennia.utils.evmenu.EvMenu class. This is the most common way to do so - from inside a Command:

11.25. EvMenu 195 Evennia Documentation, Release 0.9

# in, for example gamedir/commands/command.py from evennia.utils.evmenu import EvMenu class CmdTestMenu(Command):

key="testcommand"

def func(self):

EvMenu(caller,"world.mymenu")

When running this command, the menu will start using the menu nodes loaded from mygame/world/mymenu.py. See next section on how to define menu nodes.

The EvMenu has the following optional callsign:

EvMenu(caller, menu_data, startnode="start", cmdset_mergetype="Replace", cmdset_priority=1, auto_quit=True, auto_look=True, auto_help=True, cmd_on_exit="look", persistent=False, startnode_input="", session=None, debug=False, **kwargs)

• caller (Object or Account): is a reference to the object using the menu. This object will get a new CmdSet assigned to it, for handling the menu. • menu_data (str, module or dict): is a module or python path to a module where the global-level functions will each be considered to be a menu node. Their names in the module will be the names by which they are referred to in the module. Importantly, function names starting with an underscore _ will be ignored by the loader. Alternatively, this can be a direct mapping {"nodename":function, ...}. • startnode (str): is the name of the menu-node to start the menu at. Changing this means that you can jump into a menu tree at different positions depending on circumstance and thus possibly re-use menu entries. • cmdset_mergetype (str): This is usually one of “Replace” or “Union” (see CmdSets. The first means that the menu is exclusive - the user has no access to any other commands while in the menu. The Union mergetype means the menu co-exists with previous commands (and may overload them, so be careful as to what to name your menu entries in this case). • cmdset_priority (int): The priority with which to merge in the menu cmdset. This allows for advanced usage. • auto_quit, auto_look, auto_help (bool): If either of these are True, the menu automatically makes a quit, look or help command available to the user. The main reason why you’d want to turn this off is if you want to use the aliases “q”, “l” or “h” for something in your menu. Nevertheless, at least quit is highly recommend - if False, the menu must itself supply an “exit node” (a node without any options), or the user will be stuck in the menu until the server reloads (or eternally if the menu is persistent)! • cmd_on_exit (str): This command string will be executed right after the menu has closed down. From experience, it’s useful to trigger a “look” command to make sure the user is aware of the change of state; but any command can be used. If set to None, no command will be triggered after exiting the menu.

196 Chapter 11. Server Components Evennia Documentation, Release 0.9

• persistent (bool) - if True, the menu will survive a reload (so the user will not be kicked out by the reload - make sure they can exit on their own!) • startnode_input (str or (str, dict) tuple): Pass an input text or a input text + kwargs to the start node as if it was entered on a fictional previous node. This can be very useful in order to start a menu differently depending on the Command’s arguments in which it was initialized. • session (Session): Useful when calling the menu from an Account in MULTISESSION_MODDE higher than 2, to make sure only the right Session sees the menu output. • debug (bool): If set, the menudebug command will be made available in the menu. Use it to list the current state of the menu and use menudebug to inspect a specific state variable from the list. • All other keyword arguments will be available as initial data for the nodes. They will be available in all nodes as properties on caller.ndb._menutree (see below). These will also survive a @reload if the menu is persistent.

You don’t need to store the EvMenu instance anywhere - the very act of initializing it will store it as caller.ndb._menutree on the caller. This object will be deleted automatically when the menu is exited and you can also use it to store your own temporary variables for access throughout the menu. Temporary variables you store on a persistent _menutree as it runs will not survive a @reload, only those you set as part of the original EvMenu call.

11.25.3 The Menu nodes

The EvMenu nodes consist of functions on one of these forms.

def menunodename1(caller): # code return text, options

def menunodename2(caller, raw_string): # code return text, options

def menunodename3(caller, raw_string, **kwargs): # code return text, options

| While all of the above forms are okay, it’s recommended to stick to the third and last form since it | gives the most flexibility. The previous forms are mainly there for backwards compatibility with | existing menus from a time when EvMenu was less able.

Input arguments to the node

• caller (Object or Account): The object using the menu - usually a Character but could also be a Session or Account depending on where the menu is used. • raw_string (str): If this is given, it will be set to the exact text the user entered on the previous node (that is, the command entered to get to this node). On the starting-node of the menu, this will be an empty string, unless startnode_input was set.

11.25. EvMenu 197 Evennia Documentation, Release 0.9

• kwargs (dict): These extra keyword arguments are extra optional arguments passed to the node when the user makes a choice on the previous node. This may include things like status flags and details about which exact option was chosen (which can be impossible to determine from raw_string alone). Just what is passed in kwargs is up to you when you create the previous node.

Return values from the node

Each function must return two variables, text and options.

text

The text variable is a string or tuple. This text is what will be displayed when the user reaches this node. If this is a tuple, then the first element of the tuple will be considered the displayed text and the second the help-text to display when the user enters the help command on this node.

text=("This is the text to display","This is the help text for this node")

Returning a None text is allowed and simply leads to a node with no text and only options. If the help text is not given, the menu will give a generic error message when using help.

options

The options list describe all the choices available to the user when viewing this node. If options is returned as None, it means that this node is an Exit node - any text is displayed and then the menu immediately exits, running the exit_cmd if given.

Otherwise, options should be a list (or tuple) of dictionaries, one for each option. If only one option is available, a single dictionary can also be returned. This is how it could look:

def node_test(caller, raw_string, **kwargs):

text="A goblin attacks you!"

options=( {"key":("Attack","a","att"), "desc":"Strike the enemy with all your might", "goto":"node_attack"}, {"key":("Defend","d","def"), "desc":"Hold back and defend yourself", "goto": (_defend, {"str": 10,"enemyname":"Goblin"})})

return text, options

This will produce a menu node looking like this:

198 Chapter 11. Server Components Evennia Documentation, Release 0.9

A goblin attacks you! ______

Attack: Strike the enemy with all your might Defend: Hold back and defend yourself option-key ‘key’

The option’s key is what the user should enter in order to choose that option. If given as a tuple, the first string of that tuple will be what is shown on-screen while the rest are aliases for picking that option. In the above example, the user could enter “Attack” (or “attack”, it’s not case-sensitive), “a” or “att” in order to attack the goblin. Aliasing is useful for adding custom coloring to the choice. The first element of the aliasing tuple should then be the colored version, followed by a version without color - since otherwise the user would have to enter the color codes to select that choice.

Note that the key is optional. If no key is given, it will instead automatically be replaced with a running number starting from 1. If removing the key part of each option, the resulting menu node would look like this instead:

A goblin attacks you! ______

1: Strike the enemy with all your might 2: Hold back and defend yourself

Whether you want to use a key or rely on numbers is mostly a matter of style and the type of menu.

EvMenu accepts one important special key given only as "_default". This key is used when a user enters something that does not match any other fixed keys. It is particularly useful for getting user input:

def node_readuser(caller, raw_string, **kwargs): text="Please enter your name"

options={"key":"_default", "goto":"node_parse_input"}

return text, options

A "_default" option does not show up in the menu, so the above will just be a node saying "Please enter your name". The name they entered will appear as raw_string in the next node.

11.25. EvMenu 199 Evennia Documentation, Release 0.9

option-key ‘desc’

This simply contains the description as to what happens when selecting the menu option. For "_default" options or if the key is already long or descriptive, it is not strictly needed. But usually it’s better to keep the key short and put more detail in desc.

option-key ‘goto’

This is the operational part of the option and fires only when the user chooses said option. Here are three ways to write it

def _action_two(caller, raw_string, **kwargs): # do things ... return calculated_node_to_go_to

def _action_three(caller, raw_string, **kwargs): # do things ... return "node_four",{"mode":4} def node_select(caller, raw_string, **kwargs):

text=("select one", "help: they all do different things ...")

options=({"desc":"Option one", "goto":"node_one"}, {"desc":"Option two", "goto": _action_two} {"desc":"Option three", "goto": (_action_three, {"key":1,"key2":2})

return text, options

As seen above, goto could just be pointing to a single nodename string - the name of the node to go to. When given like this, EvMenu will look for a node named like this and call its associated function as

nodename(caller, raw_string, **kwargs)`

Here, raw_string is always the input the user entered to make that choice and kwargs are the same as those kwargs that already entered the current node (they are passed on).

Alternatively the goto could point to a “goto-callable”. Such callables are usually defined in the same module as the menu nodes and given names starting with _ (to avoid being parsed as nodes

200 Chapter 11. Server Components Evennia Documentation, Release 0.9

themselves). These callables will be called the same as a node function - callable(caller, raw_string, **kwargs), where raw_string is what the user entered on this node and **kwargs is forwarded from the node’s own input.

The goto option key could also point to a tuple (callable, kwargs) - this allows for customizing the kwargs passed into the goto-callable, for example you could use the same callable but change the kwargs passed into it depending on which option was actually chosen.

The “goto callable” must either return a string "nodename" or a tuple ("nodename", mykwargs). This will lead to the next node being called as either nodename(caller, raw_string, **kwargs) or nodename(caller, raw_string, **mykwargs) - so this allows changing (or replacing) the options going into the next node depending on what option was chosen.

There is one important case - if the goto-callable returns None for a nodename, the current node will run again, possibly with different kwargs. This makes it very easy to re-use a node over and over, for example allowing different options to update some text form being passed and manipulated for every iteration.

The EvMenu also supports the exec option key. This allows for running a callable before the goto-callable. This functionality comes from a time before goto could be a callable and is deprecated as of Evennia 0.8. Use goto for all functionality where you’d before use exec.

11.25.4 Temporary storage

When the menu starts, the EvMenu instance is stored on the caller as caller.ndb._menutree. Through this object you can in principle reach the menu’s internal state if you know what you are doing. This is also a good place to store temporary, more global variables that may be cumbersome to keep passing from node to node via the **kwargs. The _menutree will be deleted automatically when the menu closes, meaning you don’t need to worry about cleaning anything up.

If you want permanent state storage, it’s instead better to use an Attribute on caller. Remember that this will remain after the menu closes though, so you need to handle any needed cleanup yourself.

11.25.5 Customizing Menu formatting

The EvMenu display of nodes, options etc are controlled by a series of formatting methods on the EvMenu class. To customize these, simply create a new child class of EvMenu and override as needed. Here is an example:

from evennia.utils.evmenu import EvMenu

class MyEvMenu(EvMenu): (continues on next page)

11.25. EvMenu 201 Evennia Documentation, Release 0.9

(continued from previous page)

def nodetext_formatter(self, nodetext): """ Format the node text itself.

Args: nodetext (str): The full node text (the text describing the node).

Returns: nodetext (str): The formatted node text.

"""

def helptext_formatter(self, helptext): """ Format the node's help text

Args: helptext (str): The unformatted help text for the node.

Returns: helptext (str): The formatted help text.

"""

def options_formatter(self, optionlist): """ Formats the option block.

Args: optionlist (list): List of (key, description) tuples for every option related to this node. caller (Object, Account or None, optional): The caller of the node.

Returns: options (str): The formatted option display.

"""

def node_formatter(self, nodetext, optionstext): """ Formats the entirety of the node.

Args: nodetext (str): The node text as returned by `self.nodetext_formatter`. optionstext (str): The options display as returned by `self.options_

˓→formatter`. caller (Object, Account or None, optional): The caller of the node.

Returns: node (str): The formatted node to display.

"""

See evennia/utils/evmenu.py for the details of their default implementations.

202 Chapter 11. Server Components Evennia Documentation, Release 0.9

11.25.6 Examples:

• ‘Simple branching menu‘_ - choose from options • ‘Dynamic goto‘_ - jumping to different nodes based on response • ‘Set caller properties‘_ - a menu that changes things • ‘Getting arbitrary input‘_ - entering text • ‘Storing data between nodes‘_ - keeping states and information while in the menu • ‘Repeating the same node‘_ - validating within the node before moving to the next • ‘Full Menu‘_: a complete example • ‘Yes/No prompt‘_ - entering text with limited possible responses (this is not using EvMenu but the conceptu- ally similar yet technically unrelated get_input helper function accessed as evennia.utils.evmenu. get_input).

Example: Simple branching menu

Below is an example of a simple branching menu node leading to different other nodes depending on choice:

# in mygame/world/mychargen.py

def define_character(caller): text=\ """ What aspect of your character do you want to change next? """ options=({"desc":"Change the name", "goto":"set_name"}, {"desc":"Change the description", "goto":"set_description"}) return text, options

EvMenu(caller,"world.mychargen", startnode="define_character")

This will result in the following node display:

What aspect of your character do you want to change next? ______1: Change the name 2: Change the description

Note that since we didn’t specify the “name” key, EvMenu will let the user enter numbers instead. In the following examples we will not include the EvMenu call but just show nodes running inside the menu. Also, since EvMenu also takes a dictionary to describe the menu, we could have called it like this instead in the example:

EvMenu(caller, {"define_character": define_character}, startnode="define_character")

11.25. EvMenu 203 Evennia Documentation, Release 0.9

Example: Dynamic goto def _is_in_mage_guild(caller, raw_string, **kwargs): if caller.tags.get('mage', category="guild_member"): return "mage_guild_welcome" else: return "mage_guild_blocked" def enter_guild: text='You say to the mage guard:' options ({'desc':'I need to get in there.', 'goto': _is_in_mage_guild}, {'desc':'Never mind', 'goto':'end_conversation'}) return text, options

This simple callable goto will analyse what happens depending on who the caller is. The enter_guild node will give you a choice of what to say to the guard. If you try to enter, you will end up in different nodes depending on (in this example) if you have the right Tag set on yourself or not. Note that since we don’t include any ’key’s in the option dictionary, you will just get to pick between numbers.

Example: Set caller properties

Here is an example of passing arguments into the goto callable and use that to influence which node it should go to next:

def _set_attribute(caller, raw_string, **kwargs): "Get which attribute to modify and set it"

attrname, value= kwargs.get("attr",( None, None)) next_node= kwargs.get("next_node")

caller.attributes.add(attrname, attrvalue)

return next_node def node_background(caller): text=\ """ {} experienced a traumatic event in their childhood. What was it? """.format(caller.key}

options=({"key":"death", "desc":"A violent death in the family", "goto": (_set_attribute, {"attr":("experienced_violence", True), "next_node":"node_violent_background"})}, {"key":"betrayal", "desc":"The betrayal of a trusted grown-up", (continues on next page)

204 Chapter 11. Server Components Evennia Documentation, Release 0.9

(continued from previous page) "goto": (_set_attribute, {"attr":("experienced_betrayal", True), "next_node":"node_betrayal_background"})}) return text, options

This will give the following output:

Kovash the magnificent experienced a traumatic event in their childhood. What was it? ______death: A violent death in the family betrayal: The betrayal of a trusted grown-up

Note above how we use the _set_attribute helper function to set the attribute depending on the User’s choice. In thie case the helper function doesn’t know anything about what node called it - we even tell it which nodename it should return, so the choices leads to different paths in the menu. We could also imagine the helper function analyzing what other choices

Example: Get arbitrary input

An example of the menu asking the user for input - any input. def _set_name(caller, raw_string, **kwargs):

inp= raw_string.strip()

prev_entry= kwargs.get("prev_entry")

if not inp: # a blank input either means OK or Abort if prev_entry: caller.key= prev_entry caller.msg("Set name to {}.".format(prev_entry)) return "node_background" else: caller.msg("Aborted.") return "node_exit" else: # re-run old node, but pass in the name given return None,{"prev_entry": inp} def enter_name(caller, raw_string, **kwargs):

# check if we already entered a name before prev_entry= kwargs.get("prev_entry")

if prev_entry: text="Current name: {}.\nEnter another name or to accept." else: text="Enter your character's name or to abort."

options={"key":"_default", "goto": (_set_name, {"prev_entry": prev_entry})} (continues on next page)

11.25. EvMenu 205 Evennia Documentation, Release 0.9

(continued from previous page)

return text, options

This will display as

Enter your character's name or to abort.

> Gandalf

Current name: Gandalf Enter another name or to accept.

>

Set name to Gandalf.

Here we re-use the same node twice for reading the input data from the user. Whatever we enter will be caught by the _default option and passed into the helper function. We also pass along whatever name we have entered before. This allows us to react correctly on an “empty” input - continue to the node named "node_background" if we accept the input or go to an exit node if we presses Return without entering anything. By returning None from the helper function we automatically re-run the previous node, but updating its ingoing kwargs to tell it to display a different text.

Example: Storing data between nodes

A convenient way to store data is to store it on the caller.ndb._menutree which you can reach from every node. The advantage of doing this is that the _menutree NAttribute will be deleted automatically when you exit the menu.

def _set_name(caller, raw_string, **kwargs):

caller.ndb._menutree.charactersheet={} caller.ndb._menutree.charactersheet['name']= raw_string caller.msg("You set your name to {}".format(raw_string) return "background" def node_set_name(caller): text='Enter your name:' options={'key':'_default', 'goto': _set_name}

return text, options

... def node_view_sheet(caller): text="Character sheet: \n {}".format(self.ndb._menutree.charactersheet)

options=({"key":"Accept", (continues on next page)

206 Chapter 11. Server Components Evennia Documentation, Release 0.9

(continued from previous page) "goto":"finish_chargen"}, {"key":"Decline", "goto":"start_over"})

return text, options

Instead of passing the character sheet along from node to node through the kwargs we instead set it up temporarily on caller.ndb._menutree.charactersheet. This makes it easy to reach from all nodes. At the end we look at it and, if we accept the character the menu will likely save the result to permanent storage and exit.

One point to remember though is that storage on caller.ndb._menutree is not persistent across @reloads. If you are using a persistent menu (using EvMenu(..., persistent=True) you should use caller.db to store in-menu data like this as well. You must then yourself make sure to clean it when the user exits the menu.

Example: Repeating the same node

Sometimes you want to make a chain of menu nodes one after another, but you don’t want the user to be able to continue to the next node until you have verified that what they input in the previous node is ok. A common example is a login menu:

def _check_username(caller, raw_string, **kwargs): # we assume lookup_username() exists if not lookup_username(raw_string): # re-run current node by returning `None` caller.msg("|rUsername not found. Try again.") return None else: # username ok - continue to next node return "node_password" def node_username(caller): text="Please enter your user name." options={"key":"_default", "goto": _check_username} return text, options def _check_password(caller, raw_string, **kwargs):

nattempts= kwargs.get("nattempts",0) if nattempts>3: caller.msg("Too many failed attempts. Logging out") return "node_abort" elif not validate_password(raw_string): caller.msg("Password error. Try again.") (continues on next page)

11.25. EvMenu 207 Evennia Documentation, Release 0.9

(continued from previous page) return None,{"nattempts", nattempts+1} else: # password accepted return "node_login" def node_password(caller, raw_string, **kwargs): text="Enter your password." options={"key":"_default", "goto": _check_password} return text, options

This will display something like

------Please enter your username. ------

> Fo

------Username not found. Try again. ______abort: (back to start) ------

> Foo

------Please enter your password. ------

> Bar

------Password error. Try again. ------

And so on.

Here the goto-callables will return to the previous node if there is an error. In the case of password attempts, this will tick up the nattempts argument that will get passed on from iteration to iteration until too many attempts have been made.

Defining nodes in a dictionary

You can also define your nodes directly in a dictionary to feed into the EvMenu creator. def mynode(caller): # a normal menu node function return text, options menu_data={"node1": mynode, "node2": lambda caller: ( (continues on next page)

208 Chapter 11. Server Components Evennia Documentation, Release 0.9

(continued from previous page) "This is the node text", ({"key":"lambda node 1", "desc":"go to node 1 (mynode)", "goto":"node1"}, {"key":"lambda node 2", "desc":"go to thirdnode", "goto":"node3"})), "node3": lambda caller, raw_string: ( # ... etc ) }

# start menu, assuming 'caller' is available from earlier EvMenu(caller, menu_data, startnode="node1")

The keys of the dictionary become the node identifiers. You can use any callable on the right form to describe each node. If you use Python lambda expressions you can make nodes really on the fly. If you do, the lambda expression must accept one or two arguments and always return a tuple with two elements (the text of the node and its options), same as any menu node function.

Creating menus like this is one way to present a menu that changes with the circumstances - you could for example remove or add nodes before launching the menu depending on some criteria. The drawback is that a lambda expression ‘is much more limited‘_ than a full function - for example you can’t use other Python keywords like if inside the body of the lambda.

Unless you are dealing with a relatively simple dynamic menu, defining menus with lambda’s is probably more work than it’s worth: You can create dynamic menus by instead making each node function more clever. See the ‘NPC shop tutorial‘_ for an example of this.

11.25.7 Ask for simple input

This describes two ways for asking for simple questions from the user. Using Python’s input will not work in Evennia. input will block the entire server for everyone until that one player has entered their text, which is not what you want.

The yield way

In the func method of your Commands (only) you can use Python’s built-in yield command to request input in a similar way to input. It looks like this:

result= yield("Please enter your answer:")

This will send “Please enter your answer” to the Command’s self.caller and then pause at that point. All other players at the server will be unaffected. Once caller enteres a reply, the code

11.25. EvMenu 209 Evennia Documentation, Release 0.9

execution will continue and you can do stuff with the result. Here is an example:

from evennia import Command class CmdTestInput(Command): key="test" def func(self): result= yield("Please enter something:") self.caller.msg(f"You entered {result}.") result2= yield("Now enter something else:") self.caller.msg(f"You now entered {result2}.")

Using yield is simple and intuitive, but it will only access input from self.caller and you cannot abort or time out the pause until the player has responded. Under the hood, it is actually just a wrapper calling get_input described in the following section.

Important Note: In Python you cannot mix ‘‘yield‘‘ and ‘‘return ‘‘ in the same method. It has to do with yield turning the method into a ‘generator‘_.A return without an argument works, you can just not do return . This is usually not something you need to do in func() anyway, but worth keeping in mind.

The get_input way

The evmenu module offers a helper function named get_input. This is wrapped by the yield statement which is often easier and more intuitive to use. But get_input offers more flexibility and power if you need it. While in the same module as EvMenu, get_input is technically unrelated to it. The get_input allows you to ask and receive simple one-line input from the user without launching the full power of a menu to do so. To use, call get_input like this:

get_input(caller, prompt, callback)

Here caller is the entity that should receive the prompt for input given as prompt. The callback is a callable function(caller, prompt, user_input) that you define to handle the answer from the user. When run, the caller will see prompt appear on their screens and any text they enter will be sent into the callback for whatever processing you want.

Below is a fully explained callback and example call:

from evennia import Command from evennia.utils.evmenu import get_input

def callback(caller, prompt, user_input): """ This is a callback you define yourself.

(continues on next page)

210 Chapter 11. Server Components Evennia Documentation, Release 0.9

(continued from previous page) Args: caller (Account or Object): The one being asked for input prompt (str): A copy of the current prompt user_input (str): The input from the account.

Returns: repeat (bool): If not set or False, exit the input prompt and clean up. If returning anything True, stay in the prompt, which means this callback will be called again with the next user input. """ caller.msg(f"When asked' {prompt}', you answered' {user_input}'.") get_input(caller,"Write something!", callback)

This will show as

Write something! > Hello When asked 'Write something!', you answered 'Hello'.

Normally, the get_input function quits after any input, but as seen in the example docs, you could return True from the callback to repeat the prompt until you pass whatever check you want.

Note: You cannot link consecutive questions by putting a new get_input call inside the callback If you want that you should use an EvMenu instead (see the Repeating the same node example above). Otherwise you can either peek at the implementation of get_input and implement your own mechanism (it’s just using cmdset nesting) or you can look at ‘this extension suggested on the mailing list‘_.

Example: Yes/No prompt

Below is an example of a Yes/No prompt using the get_input function: def yesno(caller, prompt, result): if result.lower() in ("y","yes","n","no"): # do stuff to handle the yes/no answer # ... # if we return None/False the prompt state # will quit after this else: # the answer is not on the right yes/no form caller.msg("Please answer Yes or No. \n{prompt}") @ # returning True will make sure the prompt state is not exited return True

# ask the question get_input(caller,"Is Evennia great (Yes/No)?", yesno)

11.25. EvMenu 211 Evennia Documentation, Release 0.9

11.25.8 The @list_node decorator

The evennia.utils.evmenu.list_node is an advanced decorator for use with EvMenu node functions. It is used to quickly create menus for manipulating large numbers of items.

text here ______

1. option1 7. option7 13. option13 2. option2 8. option8 14. option14 3. option3 9. option9 [p]revius page 4. option4 10. option10 page2 5. option5 11. option11 [n]ext page 6. option6 12. option12

The menu will automatically create an multi-page option listing that one can flip through. One can inpect each entry and then select them with prev/next. This is how it is used:

from evennia.utils.evmenu import list_node

...

_options(caller): return ['option1','option2',...'option100']

_select(caller, menuchoice, available_choices): # analyze choice return "next_node"

@list_node(options, select=_select, pagesize=10) def node_mylist(caller, raw_string, **kwargs): ...

return text, options

The options argument to list_node is either a list, a generator or a callable returning a list of strings for each option that should be displayed in the node.

The select is a callable in the example above but could also be the name of a menu node. If a callable, the menuchoice argument holds the selection done and available_choices holds all the options available. The callable should return the menu to go to depending on the selection (or None to rerun the same node). If the name of a menu node, the selection will be passed as selection kwarg to that node.

The decorated node itself should return text to display in the node. It must return at least an empty dictionary for its options. It returning options, those will supplement the options

212 Chapter 11. Server Components Evennia Documentation, Release 0.9 auto-created by the list_node decorator.

11.25.9 Assorted notes

The EvMenu is implemented using ‘Commands‘_. When you start a new EvMenu, the user of the menu will be assigned a CmdSet with the commands they need to navigate the menu. This means that if you were to, from inside the menu, assign a new command set to the caller, you may override the Menu Cmdset and kill the menu. If you want to assign cmdsets to the caller as part of the menu, you should store the cmdset on caller.ndb._menutree and wait to actually assign it until the exit node. limited: https://docs.python.org/2/tutorial/controlflow.html#lambda-expressions .. _NPC shop tutorial: NPC- shop-Tutorial.html .. _generator: https://www.learnpython.org/en/Generators .. _Repeating the same node: EvMenu#example-repeating-the-same-node .. _this extension suggested on the mailing list: https://groups.google. com/forum/#!category-topic/evennia/evennia-questions/16pi0SfMO5U .. _Commands: Commands.html

11.26 EvMore

When sending a very long text to a user client, it might scroll beyond of the height of the client window. The evennia.utils.evmore.EvMore class gives the user the in-game ability to only view one page of text at a time. It is usually used via its access function, evmore.msg.

The name comes from the famous unix pager utility more which performs just this function.

11.26.1 Using EvMore

To use the pager, just pass the long text through it: from evennia.utils import evmore evmore.msg(receiver, long_text)

Where receiver is an Object or a Account. If the text is longer than the client’s screen height (as determined by the NAWS handshake or by settings.CLIENT_DEFAULT_HEIGHT) the pager will show up, something like this:

[. . . ] aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. (more [1/6] return|back|top|end|abort)

11.26. EvMore 213 Evennia Documentation, Release 0.9

where the user will be able to hit the return key to move to the next page, or use the suggested commands to jump to previous pages, to the top or bottom of the document as well as abort the paging.

The pager takes several more keyword arguments for controlling the message output. See the evmore-API for more info.

11.27 EvEditor

Evennia offers a powerful in-game line editor in evennia.utils.eveditor.EvEditor. This editor, mimick- ing the well-known VI line editor. It offers line-by-line editing, undo/redo, line deletes, search/replace, fill, dedent and more.

11.27.1 Launching the editor

The editor is created as follows:

from evennia.utils.eveditor import EvEditor

EvEditor(caller, loadfunc=None, savefunc=None, quitfunc=None, key="")

• caller (Object or Account): The user of the editor. • loadfunc (callable, optional): This is a function called when the editor is first started. It is called with caller as its only argument. The return value from this function is used as the starting text in the editor buffer. • savefunc (callable, optional): This is called when the user saves their buffer in the editor is called with two arguments, caller and buffer, where buffer is the current buffer. • quitfunc (callable, optional): This is called when the user quits the editor. If given, all cleanup and exit messages to the user must be handled by this function. • key (str, optional): This text will be displayed as an identifier and reminder while editing. It has no other mechanical function. • persistent (default False): if set to True, the editor will survive a reboot.

11.27.2 Example of usage

This is an example command for setting a specific Attribute using the editor.

from evennia import Command from evennia.utils import eveditor

class CmdSetTestAttr(Command): """ Set the "test" Attribute using the line editor.

Usage: (continues on next page)

214 Chapter 11. Server Components Evennia Documentation, Release 0.9

(continued from previous page) settestattr

""" key="settestattr" def func(self): "Set up the callbacks and launch the editor" def load(caller): "get the current value" return caller.attributes.get("test") def save(caller, buffer): "save the buffer" caller.attributes.set("test", buffer) def quit(caller): "Since we define it, we must handle messages" caller.msg("Editor exited") key=" %s/test"% self.caller # launch the editor eveditor.EvEditor(self.caller, loadfunc=load, savefunc=save, quitfunc=quit, key=key)

11.27.3 Persistent editor

If you set the persistent keyword to True when creating the editor, it will remain open even when reloading the game. In order to be persistent, an editor needs to have its callback functions (loadfunc, savefunc and quitfunc) as top-level functions defined in the module. Since these functions will be stored, Python will need to find them. from evennia import Command from evennia.utils import eveditor def load(caller): "get the current value" return caller.attributes.get("test") def save(caller, buffer): "save the buffer" caller.attributes.set("test", buffer) def quit(caller): "Since we define it, we must handle messages" caller.msg("Editor exited") class CmdSetTestAttr(Command): """ Set the "test" Attribute using the line editor.

Usage: settestattr

""" key="settestattr" def func(self): "Set up the callbacks and launch the editor" (continues on next page)

11.27. EvEditor 215 Evennia Documentation, Release 0.9

(continued from previous page) key=" %s/test"% self.caller # launch the editor eveditor.EvEditor(self.caller, loadfunc=load, savefunc=save, quitfunc=quit, key=key, persistent=True)

11.27.4 Line editor usage

The editor mimics the VIM editor as best as possible. The below is an excerpt of the return from the in-editor help command (:h).

- any non-command is appended to the end of the buffer. : - view buffer or only line :: - view buffer without line numbers or other parsing ::: - print a ':' as the only character on the line... :h - this help.

: - save the buffer (don't quit) :wq - save buffer and quit :q - quit (will be asked to save if buffer was changed) :q! - quit without saving, no questions asked

:u - (undo) step backwards in undo history :uu - (redo) step forward in undo history :UU - reset all changes back to initial state

:dd - delete line :dw - delete word or regex in entire buffer or on line :DD - clear buffer

:y - yank (copy) line to the copy buffer :x - cut line and store it in the copy buffer :p - put (paste) previously copied line directly after :i - insert new text at line . Old line will move down :r - replace line with text :I - insert text at the beginning of line :A - append text after the end of line

:s - search/replace word or regex in buffer or on line

:f - flood-fill entire buffer or line :fi - indent entire buffer or line :fd - de-indent entire buffer or line

:echo - turn echoing of the input on/off (helpful for some clients)

Legend: - line numbers, or range lstart:lend, e.g. '3:7'. - one word or several enclosed in quotes. - longer string, usually not needed to be enclosed in quotes.

216 Chapter 11. Server Components Evennia Documentation, Release 0.9

11.27.5 The EvEditor to edit code

The EvEditor is also used to edit some Python code in Evennia. The @py command supports an /edit switch that will open the EvEditor in code mode. This mode isn’t significantly different from the standard one, except it handles automatic indentation of blocks and a few options to control this behavior. • :< to remove a level of indentation for the future lines. • :+ to add a level of indentation for the future lines. • := to disable automatic indentation altogether. Automatic indentation is there to make code editing more simple. Python needs correct indentation, not as an aesthetic addition, but as a requirement to determine beginning and ending of blocks. The EvEditor will try to guess the next level of indentation. If you type a block “if”, for instance, the EvEditor will propose you an additional level of indentation at the next line. This feature cannot be perfect, however, and sometimes, you will have to use the above options to handle indentation. := can be used to turn automatic indentation off completely. This can be very useful when trying to paste several lines of code that are already correctly indented, for instance. To see the EvEditor in code mode, you can use the @py/edit command. Type in your code (on one or several lines). You can then use the :w option (save without quitting) and the code you have typed will be executed. The :! will do the same thing. Executing code while not closing the editor can be useful if you want to test the code you have typed but add new lines after your test.

11.28 Web Features

Evennia is its own webserver and hosts a default website and browser webclient.

11.28.1 Web site

The Evennia website is a Django application that ties in with the MUD database. Since the website shares this database you could, for example, tell website visitors how many accounts are logged into the game at the moment, how long the server has been up and any other database information you may want. During development you can access the website by pointing your browser to http://localhost:4001. You may also want to set DEBUG = True in your settings file for debugging the website. You will then see proper tracebacks in the browser rather than just error codes. Note however that this will leak memory a lot (it stores everything all the time) and is not to be used in production. It’s recommended to only use DEBUG for active web development and to turn it off otherwise. A Django (and thus Evennia) website basically consists of three parts, a view an associated template and an urls.py file. Think of the view as the Python back-end and the template as the HTML files you are served, optionally filled with data from the back-end. The urls file is a sort of mapping that tells Django that if a specific URL is given in the browser, a particular view should be triggered. You are wise to review the Django documentation for details on how to use these components. Evennia’s default website is located in evennia/web/website. In this folder you’ll find the simple default view as well as subfolders templates and static. Static files are things like images, CSS files and Javascript.

Customizing the Website

You customize your website from your game directory. In the folder web you’ll find folders static, templates, static_overrides and templates_overrides. The first two of those are populated automatically by

11.28. Web Features 217 Evennia Documentation, Release 0.9

Django and used to serve the website. You should not edit anything in them - the change will be lost. To customize the website you’ll need to copy the file you want to change from the web/website/template/ or web/website/ static/ path to the corresponding place under one of_overrides‘ directories. Example: To override or modify evennia/web/website/template/website/index.html you need to add/modify mygame/web/template_overrides/website/index.html. The detailed description on how to customize the website is best described in tutorial form. See the Web Tutorial for more information.

Overloading Django views

The Python backend for every HTML page is called a Django view. A view can do all sorts of functions, but the main one is to update variables data that the page can display, like how your out-of-the-box website will display statistics about number of users and database objects. To re-point a given page to a view.py of your own, you need to modify mygame/web/urls.py. An URL pattern is a regular expression that you need to enter in the address field of your web browser to get to the page in question. If you put your own URL pattern before the default ones, your own view will be used instead. The file urls.py even marks where you should put your change. Here’s an example:

# mygame/web/urls.py

from django.conf.urls import url, include # default patterns from evennia.web.urls import urlpatterns

# our own view to use as a replacement from web.myviews import myview

# custom patterns to add patterns=[ # overload the main page view url(r'^', myview, name='mycustomview'), ]

urlpatterns= patterns+ urlpatterns

Django will always look for a list named urlpatterns which consists of the results of url() calls. It will use the first match it finds in this list. Above, we add a new URL redirect from the root of the website. It will now our own function myview from a new module mygame/web/myviews.py. If our game is found on http://mygame.com, the regular expression "^" means we just entered mygame.com in the address bar. If we had wanted to add a view for http://mygame.com/ awesome, the regular expression would have been ^/awesome. Look at evennia/web/website/views.py to see the inputs and outputs you must have to define a view. Easiest may be to copy the default file to mygame/web to have something to modify and expand on. Restart the server and reload the page in the browser - the website will now use your custom view. If there are errors, consider turning on settings.DEBUG to see the full tracebacks - in debug mode you will also log all requests in mygame/server/logs/http_requests.log.

11.28.2 Web client

Evennia comes with a MUD client accessible from a normal web browser. During

218 Chapter 11. Server Components Evennia Documentation, Release 0.9 development you can try it at http://localhost:4001/webclient. See the Webclient page for more details.

11.28.3 The Django ‘Admin’ Page

Django comes with a built-in admin website. This is accessible by clicking the ‘admin’ button from your game website. The admin site allows you to see, edit and create objects in your database from a graphical interface. The behavior of default Evennia models are controlled by files admin.py in the Evennia package. New database models you choose to add yourself (such as in the Web Character View Tutorial) can/will also have admin.py files. New models are registered to the admin website by a call of admin.site.register(model class, admin class) inside an admin.py file. It is an error to attempt to register a model that has already been registered. To overload Evennia’s admin files you don’t need to modify Evennia itself. To customize you can call admin.site. unregister(model class), then follow that with admin.site.register in one of your own admin.py files in a new app that you add.

11.28.4 More reading

Evennia relies on Django for its web features. For details on expanding your web experience, the Django documenta- tion or the Django Book are the main resources to look into. In Django lingo, the Evennia is a django “project” that consists of Django “applications”. For the sake of web implementation, the relevant django “applications” in default Evennia are web/website or web/webclient.

11.28. Web Features 219 Evennia Documentation, Release 0.9

220 Chapter 11. Server Components CHAPTER 12

Coding utilities

This chapter explains various coding utilities and coding practices useful for working with Evennia.

12.1 New Models

Note: This is considered an advanced topic. Evennia offers many convenient ways to store object data, such as via Attributes or Scripts. This is sufficient for most use cases. But if you aim to build a large stand-alone system, trying to squeeze your storage requirements into those may be more complex than you bargain for. Examples may be to store guild data for guild members to be able to change, tracking the flow of money across a game-wide economic system or implement other custom game systems that requires the storage of custom data in a quickly accessible way. Whereas Tags or Scripts can handle many situations, sometimes things may be easier to handle by adding your own database model.

12.1.1 Overview of database tables

SQL-type databases (which is what Evennia supports) are basically highly optimized systems for retrieving text stored in tables. A table may look like this

id| db_key| db_typeclass_path| db_permissions... ------1| Griatch| evennia.DefaultCharacter| Developers... 2| Rock| evennia.DefaultObject| None ...

Each line is considerably longer in your database. Each column is referred to as a “field” and every row is a separate object. You can check this out for yourself. If you use the default sqlite3 database, go to your game folder and run evennia dbshell

You will drop into the database shell. While there, try:

221 Evennia Documentation, Release 0.9

sqlite>.help # view help sqlite>.tables # view all tables

# show the table field names for objects_objectdb sqlite>.schema objects_objectdb

# show the first row from the objects_objectdb table sqlite> select * from objects_objectdb limit1; sqlite>.exit

Evennia uses Django, which abstracts away the database SQL manipulation and allows you to search and manipulate your database entirely in Python. Each database table is in Django represented by a class commonly called a model since it describes the look of the table. In Evennia, Objects, Scripts, Channels etc are examples of Django models that we then extend and build on.

12.1.2 Adding a new database table

Here is how you add your own database table/models: 1. In Django lingo, we will create a new “application” - a subsystem under the main Evennia program. For this example we’ll call it “myapp”. Run the following (you need to have a working Evennia running before you do this, so make sure you have run the steps in Getting Started first):

cd mygame/world evennia startapp myapp

2. A new folder myapp is created. “myapp” will also be the name (the “app label”) from now on. We chose to put it in the world/ subfolder here, but you could put it in the root of your mygame if that makes more sense. 3. The myapp folder contains a few empty default files. What we are interested in for now is models.py. In models.py you define your model(s). Each model will be a table in the database. See the next section and don’t continue until you have added the models you want. 4. You now need to tell Evennia that the models of your app should be a part of your database scheme. Add this line to your mygame/server/conf/settings.pyfile (make sure to use the path where you put myapp and don’t forget the comma at the end of the tuple):

INSTALLED_APPS= INSTALLED_APPS+("world.myapp",)

5. From mygame/, run

evennia makemigrations myapp evennia migrate

This will add your new database table to the database. If you have put your game under version control (if not, you should), don’t forget to git add myapp/* to add all items to version control.

12.1.3 Defining your models

A Django model is the Python representation of a database table. It can be handled like any other Python class. It defines fields on itself, objects of a special type. These become the “columns” of the database table. Finally, you create new instances of the model to add new rows to the database.

222 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

We won’t describe all aspects of Django models here, for that we refer to the vast Django documentation on the subject. Here is a (very) brief example: from django.db import models class MyDataStore(models.Model): "A simple model for storing some data" db_key= models.CharField(max_length=80, db_index= True) db_category= models.CharField(max_length=80, null= True, blank=True) db_text= models.TextField(null= True, blank=True) # we need this one if we want to be # able to store this in an Evennia Attribute! db_date_created= models.DateTimeField('date created', editable= False, auto_now_add=True, db_index=True)

We create four fields: two character fields of limited length and one text field which has no maximum length. Finally we create a field containing the current time of us creating this object. The db_date_created field, with exactly this name, is required if you want to be able to store instances of your custom model in an Evennia Attribute. It will automatically be set upon cre- ation and can after that not be changed. Having this field will allow you to do e.g. obj.db. myinstance = mydatastore. If you know you’ll never store your model instances in Attributes the db_date_created field is optional. You don’t have to start field names with db_, this is an Evennia convention. It’s nevertheless recommended that you do use db_, partly for clarity and consistency with Evennia (if you ever want to share your code) and partly for the case of you later deciding to use Evennia’s SharedMemoryModel parent down the line. The field keyword db_index creates a database index for this field, which allows quicker lookups, so it’s recom- mended to put it on fields you know you’ll often use in queries. The null=True and blank=True keywords means that these fields may be left empty or set to the empty string without the database complaining. There are many other field types and keywords to define them, see django docs for more info. Similar to using django-admin you are able to do evennia inspectdb to get an automated listing of model information for an existing database. As is the case with any model generating tool you should only use this as a starting point for your models.

12.1.4 Creating a new model instance

To create a new row in your table, you instantiate the model and then call its save() method: from evennia.myapp import MyDataStore new_datastore= MyDataStore(db_key="LargeSword", db_category="weapons", db_text="This is a huge weapon!") # this is required to actually create the row in the database! new_datastore.save()

Note that the db_date_created field of the model is not specified. Its flag at_now_add=True makes sure to set it to the current date when the object is created (it can also not be changed further after creation). When you update an existing object with some new field value, remember that you have to save the object afterwards, otherwise the database will not update: my_datastore.db_key="Larger Sword" my_datastore.save()

12.1. New Models 223 Evennia Documentation, Release 0.9

Evennia’s normal models don’t need to explicitly save, since they are based on SharedMemoryModel rather than the raw django model. This is covered in the next section.

12.1.5 Using the SharedMemoryModel parent

Evennia doesn’t base most of its models on the raw django.db.models but on the Evennia base model evennia. utils.idmapper.models.SharedMemoryModel. There are two main reasons for this: 1. Ease of updating fields without having to explicitly call save() 2. On-object memory persistence and database caching The first (and least important) point means that as long as you named your fields db_*, Evennia will automatically create field wrappers for them. This happens in the model’s Metaclass so there is no speed penalty for this. The name of the wrapper will be the same name as the field, minus the db_ prefix. So the db_key field will have a wrapper property named key. You can then do:

my_datastore.key="Larger Sword"

and don’t have to explicitly call save() afterwards. The saving also happens in a more efficient way under the hood, updating only the field rather than the entire model using django optimizations. Note that if you were to manually add the property or method key to your model, this will be used instead of the automatic wrapper and allows you to fully customize access as needed. To explain the second and more important point, consider the following example using the default Django model parent:

shield= MyDataStore.objects.get(db_key="SmallShield") shield.cracked= True # where cracked is not a database field

And then later:

shield= MyDataStore.objects.get(db_key="SmallShield") print(shield.cracked) # error!

The outcome of that last print statement is undefined! It could maybe randomly work but most likely you will get an AttributeError for not finding the cracked property. The reason is that cracked doesn’t represent an actual field in the database. It was just added at run-time and thus Django don’t care about it. When you retrieve your shield-match later there is no guarantee you will get back the same Python instance of the model where you defined cracked, even if you search for the same database object. Evennia relies heavily on on-model handlers and other dynamically created properties. So rather than using the vanilla Django models, Evennia uses SharedMemoryModel, which levies something called idmapper. The idmapper caches model instances so that we will always get the same instance back after the first lookup of a given object. Using idmapper, the above example would work fine and you could retrieve your cracked property at any time - until you rebooted when all non-persistent data goes. Using the idmapper is both more intuitive and more efficient per object; it leads to a lot less reading from disk. The drawback is that this system tends to be more memory hungry overall. So if you know that you’ll never need to add new properties to running instances or know that you will create new objects all the time yet rarely access them again (like for a log system), you are probably better off making “plain” Django models rather than using SharedMemoryModel and its idmapper. To use the idmapper and the field-wrapper functionality you just have to have your model classes inherit from evennia.utils.idmapper.models.SharedMemoryModel instead of from the default django.db. models.Model:

224 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

from evennia.utils.idmapper.models import SharedMemoryModel

class MyDataStore(SharedMemoryModel): # the rest is the same as before, but db_* is important; these will # later be settable as .key, .category, .text ... db_key= models.CharField(max_length=80, db_index= True) db_category= models.CharField(max_length=80, null= True, blank=True) db_text= models.TextField(null= True, blank=True) db_date_created= models.DateTimeField('date created', editable= False, auto_now_add=True, db_index=True)

12.1.6 Searching for your models

To search your new custom database table you need to use its database manager to build a query. Note that even if you use SharedMemoryModel as described in the previous section, you have to use the actual field names in the query, not the wrapper name (so db_key and not just key).

from world.myapp import MyDataStore

# get all datastore objects exactly matching a given key matches= MyDataStore.objects.filter(db_key="Larger Sword") # get all datastore objects with a key containing "sword" # and having the category "weapons" (both ignoring upper/lower case) matches2= MyDataStore.objects.filter(db_key__icontains="sword", db_category__iequals="weapons") # show the matching data (e.g. inside a command) for match in matches2: self.caller.msg(match.db_text)

See the Django query documentation for a lot more information about querying the database.

12.2 Coding Utils

Evennia comes with many utilities to help with common coding tasks. Most are accessible directly from the flat API, otherwise you can find them in the evennia/utils/ folder.

12.2.1 Searching

A common thing to do is to search for objects. There it’s easiest to use the search method defined on all objects. This will search for objects in the same location and inside the self object:

obj= self.search(objname)

The most common time one needs to do this is inside a command body. obj = self.caller. search(objname) will search inside the caller’s (typically, the character that typed the command) .contents (their “inventory”) and .location (their “room”). Give the keyword global_search=True to extend search to encompass entire database. Aliases will also be matched by this search. You will find multiple examples of this functionality in the default command set. If you need to search for objects in a code module you can use the functions in evennia.utils.search. You can access these as shortcuts evennia.search_*.

12.2. Coding Utils 225 Evennia Documentation, Release 0.9

from evennia import search_object obj= search_object(objname)

• evennia.search_account • evennia.search_object • evennia.search_object_by_tag • evennia.search_script • evennia.search_channel • evennia.search_message • evennia.search_help Note that these latter methods will always return a list of results, even if the list has one or zero entries.

12.2.2 Create

Apart from the in-game build commands (@create etc), you can also build all of Evennia’s game entities directly in code (for example when defining new create commands).

import evennia

myobj= evennia.create_objects("game.gamesrc.objects.myobj.MyObj", key="MyObj")

• evennia.create_account • evennia.create_object • evennia.create_script • evennia.create_channel • evennia.create_help_entry • evennia.create_message Each of these create-functions have a host of arguments to further customize the created entity. See evennia/ utils/create.py for more information.

12.2.3 Logging

Normally you can use Python print statements to see output to the terminal/log. The print statement should only be used for debugging though. For producion output, use the logger which will create proper logs either to terminal or to file.

from evennia import logger # logger.log_err("This is an Error!") logger.log_warn("This is a Warning!") logger.log_info("This is normal information") logger.log_dep("This feature is deprecated")

There is a special log-message type, log_trace() that is intended to be called from inside a traceback - this can be very useful for relaying the traceback message back to log without having it kill the server.

226 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

try: # [some code that may fail...] except Exception: logger.log_trace("This text will show beneath the traceback itself.")

The log_file logger, finally, is a very useful logger for outputting arbitrary log messages. This is a heavily op- timized asynchronous log mechanism using threads to avoid overhead. You should be able to use it for very heavy custom logging without fearing disk-write delays.

logger.log_file(message, filename="mylog.log")

If not an absolute path is given, the log file will appear in the mygame/server/logs/ directory. If the file already exists, it will be appended to. Timestamps on the same format as the normal Evennia logs will be automatically added to each entry. If a filename is not specified, output will be written to a file game/logs/game.log.

12.2.4 Time Utilities

Game time

Evennia tracks the current server time. You can access this time via the evennia.gametime shortcut:

from evennia import gametime

# all the functions below return times in seconds).

# total running time of the server runtime= gametime.runtime() # time since latest hard reboot (not including reloads) uptime= gametime.uptime() # server epoch (its start time) server_epoch= gametime.server_epoch()

# in-game epoch (this can be set by `settings.TIME_GAME_EPOCH`. # If not, the server epoch is used. game_epoch= gametime.game_epoch() # in-game time passed since time started running gametime= gametime.gametime() # in-game time plus game epoch (i.e. the current in-game # time stamp) gametime= gametime.gametime(absolute= True) # reset the game time (back to game epoch) gametime.reset_gametime()

The setting TIME_FACTOR determines how fast/slow in-game time runs compared to the real world. The setting TIME_GAME_EPOCH sets the starting game epoch (in seconds). The functions from the gametime module all return their times in seconds. You can convert this to whatever units of time you desire for your game. You can use the @time command to view the server time info. You can also schedule things to happen at specific in-game times using the gametime.schedule function:

import evennia

def church_clock: limbo= evennia.search_object(key="Limbo") limbo.msg_contents("The church clock chimes two.") (continues on next page)

12.2. Coding Utils 227 Evennia Documentation, Release 0.9

(continued from previous page) gametime.schedule(church_clock, hour=2) utils.time_format()

This function takes a number of seconds as input (e.g. from the gametime module above) and converts it to a nice text output in days, hours etc. It’s useful when you want to show how old something is. It converts to four different styles of output using the style keyword: • style 0 - 5d:45m:12s (standard colon output) • style 1 - 5d (shows only the longest time unit) • style 2 - 5 days, 45 minutes (full format, ignores seconds) • style 3 - 5 days, 45 minutes, 12 seconds (full format, with seconds) utils.delay() from evennia import utils def _callback(obj, text): obj.msg(text)

# wait 10 seconds before sending "Echo!" to obj (which we assume is defined) deferred= utils.delay(10, _callback, obj,"Echo!", persistent= False)

# code here will run immediately, not waiting for the delay to fire!

This creates an asynchronous delayed call. It will fire the given callback function after the given number of sec- onds. This is a very light wrapper over a Twisted Deferred. Normally this is run non-persistently, which means that if the server is @reloaded before the delay is over, the callback will never run (the server forgets it). If setting persistent to True, the delay will be stored in the database and survive a @reload - but for this to work it is susceptible to the same limitations incurred when saving to an Attribute. The deferred return object can usually be ignored, but calling its .cancel() method will abort the delay prema- turely. utils.delay is the lightest form of delayed call in Evennia. For other way to create time-bound tasks, see the TickerHandler and Scripts. Note that many delayed effects can be achieved without any need for an active timer. For example if you have a trait that should recover a point every 5 seconds you might just need its value when it’s needed, but checking the current time and calculating on the fly what value it should have.

12.2.5 Object Classes utils.inherits_from()

This useful function takes two arguments - an object to check and a parent. It returns True if object inherits from parent at any distance (as opposed to Python’s in-built is_instance() that will only catch immediate dependence). This function also accepts as input any combination of classes, instances or python-paths-to-classes.

228 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

Note that Python code should usually work with duck typing. But in Evennia’s case it can sometimes be useful to check if an object inherits from a given Typeclass as a way of identification. Say for example that we have a typeclass Animal. This has a subclass Felines which in turn has a subclass HouseCat. Maybe there are a bunch of other animal types too, like horses and dogs. Using inherits_from will allow you to check for all animals in one go:

from evennia import utils if (utils.inherits_from(obj,"typeclasses.objects.animals.Animal"): obj.msg("The bouncer stops you in the door. He says:'No talking animals allowed.'

˓→")

12.2.6 Text utilities

In a text game, you are naturally doing a lot of work shuffling text back and forth. Here is a non-complete selection of text utilities found in evennia/utils/utils.py (shortcut evennia.utils). If nothing else it can be good to look here before starting to develop a solution of your own.

utils.fill()

This flood-fills a text to a given width (shuffles the words to make each line evenly wide). It also indents as needed.

outtxt= fill(intxt, width=78, indent=4)

utils.crop()

This function will crop a very long line, adding a suffix to show the line actually continues. This can be useful in listings when showing multiple lines would mess up things.

intxt="This is a long text that we want to crop." outtxt= crop(intxt, width=19, suffix="[...]") # outtxt is now "This is a long text[...]"

utils.dedent()

This solves what may at first glance appear to be a trivial problem with text - removing indentations. It is used to shift entire paragraphs to the left, without disturbing any further formatting they may have. A common case for this is when using Python triple-quoted strings in code - they will retain whichever indentation they have in the code, and to make easily-readable source code one usually don’t want to shift the string to the left edge.

#python code is entered at a given indentation intxt= """ This is an example text that will end up with a lot of whitespace on the left. It also has indentations of its own.""" outtxt= dedent(intxt) # outtxt will now retain all internal indentation # but be shifted all the way to the left.

Normally you do the dedent in the display code (this is for example how the help system homogenizes help entries).

12.2. Coding Utils 229 Evennia Documentation, Release 0.9 to_str() and to_bytes()

Evennia supplies two utility functions for converting text to the correct encodings. to_str() and to_bytes(). Unless you are adding a custom protocol and need to send byte-data over the wire, to_str is the only one you’ll need.

The difference from Python’s in-built str() and bytes() operators are that the Evennia ones makes use of the ENCODINGS setting and will try very hard to never raise a traceback but instead echo errors through logging. See here for more info.

Ansi Coloring Tools

• evennia.ansi

12.2.7 Display utilities

Making tables

The EvTable class (evennia/utils/evtable.py) can be used to create correctly formatted text tables. There is also EvForm( evennia/utils/evform.py). This reads a fixed-format text template from a file in order to create any level of sophisticated ascii layout. Both evtable and evform have lots of options and inputs so see the header of each module for help. The third-party PrettyTable module is also included in Evennia. PrettyTable is considered deprecated in fa- vor of EvTable since PrettyTable cannot handle ANSI colour. PrettyTable can be found in evennia/utils/ prettytable/. See its homepage above for instructions.

Menus

• evennia.EvMenu

12.3 Version Control

<<<<<<< HEAD ‘‘_ Version control software allows you to track the changes you make to your code, as well as being able to easily backtrack these changes, share your development efforts and more. Even if you are not contributing to Evennia itself, and only wish to develop your own MU* using Evennia, having a version control system in place is a good idea (and standard coding practice). For an introduction to the concept start with the Wikipedia article here. Note that this page deals with commands for Linux operating systems, and the steps below may vary for other systems, however where possible links will be provided for alternative instructions.

For more help on using Git, please refer to the Official Github documentation.

230 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

12.3.1 Setting up Git

If you have gotten Evennia installed, you will have Git already and can skip to Step 2 below. Otherwise you will need to install Git on your platform. You can find expanded instructions for installation here.

Step 1: Install Git

• Fedora Linux

yum install git-core

• Debian Linux (Ubuntu, Linux Mint, etc.)

apt-get install git

• Windows: It is recommended to use Git for Windows. • Mac: Mac platforms offer two methods for installation, one via MacPorts, which you can find out about here, or you can use the Git OSX Installer.

Step 2: Define user/e-mail Settings for Git

To avoid a common issue later, you will need to set a couple of settings; first you will need to tell Git your username, followed by your e-mail address, so that when you commit code later you will be properly credited. 1. Set the default name for git to use when you commit:

git config-- global user.name"Your Name Here"

2. Set the default email for git to use when you commit:

git config-- global user.email"[email protected]"

12.3.2 Forking from Evennia

Step 1: Fork the evennia/master repository

Before proceeding with the following step, make sure you have registered and created an account on Github.com. This is necessary in order to create a fork of Evennia’s master repository, and to push your commits to your fork either for yourself or for contributing to Evennia. A fork is a clone of the master repository that you can make your own commits and changes to. At the top of this page, click the “Fork” button, as it appears below.

12.3. Version Control 231 Evennia Documentation, Release 0.9

Step 2: Clone your fork

The fork only exists online as of yet. In a terminal, change your directory to the folder you wish to develop in. From this directory run the following command:

git clone https://github.com/yourusername/evennia.git

This will download your fork to your computer. It creates a new folder evennia/ at your current location.

Step 3: Configure remotes

A remote is a repository stored on another computer, in this case on GitHub’s server. When a repository is cloned, it has a default remote called origin. This points to your fork on GitHub, not the original repository it was forked from. To easily keep track of the original repository (that is, Evennia’s official repository), you need to add another remote. The standard name for this remote is “upstream”. Below we change the active directory to the newly cloned “evennia” directory and then assign the original Evennia repository to a remote called “upstream”:

cd evennia git remote add upstream https://github.com/evennia/evennia.git

12.3.3 Working with your fork

Making a work branch

A branch is a separate instance of your code. Changes you do to code in a branch does not affect that in other branches (so if you for example add/commit a file to one branch and then switches to another branch, that file will be gone until you switch back to the first branch again). One can switch between branches at will and create as many branches as one needs for a given project. The content of branches can also be merged together or deleted without affecting other branches. This is not only a common way to organize development but also to test features without messing with existing code. The default branch of git is called the “master” branch. We will let our master branch be our “clean” Evennia install - this is a good debugging practice since it allows us to know if a bug you find is due to your changes or also visible in the core server. We’ll develop our game in another branch instead, let’s call it “mygame”.

git checkout-b mygame

This command will checkout and automatically create the new branch mygame on your machine. You can see which branch you are on with git branch and change between different branches with git checkout .

Making a branch for Evennia-fixes

If you want to contribute fixes to Evennia itself, it’s a good idea to keep those separate from your own game implementation. This makes it easier for Evennia developers to later pull in only the changes that are relevant. Get back to the master branch (git checkout master) and create a new “myfixes” branch:

git checkout-b myfixes

232 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

Tracking files

When working on your code or fix bugs in your local branches you may end up creating new files. If you do you must tell Git to track them by using the add command: git add

You can check the current status of version control with git status. This will show if you have any modified, added or otherwise changed files. Some files, like database files, logs and temporary PID files are usually not tracked in version control. These should have a question mark in front of them.

Controlling tracking

In order to not track system settings so that you and/or your team can pull without worries of directory structure it is recommended to append your .gitignore file located in the root directory evennia. Be sure that you are NOT in the branch master when appending this file or updates will not download due to the folder src being ignored. Open .gitignore, which is a hidden file (note the period at the beginning of the file name). At the end of the file add the following:

# Custom settings.py *.log *.log.old src

Now you can pull and install without having to edit the files for system-specific data. If settings.py does need to be modified, simply share with your team what needs to be edited. The reason for ignoring it is because different servers may need different settings, especially if you are working in a team and have a remote server to worry about.

Committing your Code

Committing means storing the current snapshot of your code within git. This creates a “save point” or “history” of your development process. You can later jump back and forth in your history, for example to figure out just when a bug was introduced or see what results the code used to produce compared to now. It’s usually a good idea to commit your changes often. Committing is fast and local only - you will never commit anything online at this point. To commit your changes, use git commit--all

This will save all changes you made since last commit. The command will open a text editor where you can add a message detailing the changes you’ve made. Make it brief but informative. You can see the history of commits with git log. If you don’t want to use the editor you can set the message directly by using the -m flag: git commit--all-m"This fixes a bug in the combat code."

Changing your mind

If you have non-committed changes that you realize you want to throw away, you can do the following: git checkout

12.3. Version Control 233 Evennia Documentation, Release 0.9

This will revert the file to the state it was in at your last commit, throwing away the changes you did to it since. It’s a good way to make wild experiments without having to remember just what you changed. If you do git checkout . you will throw away all changes since the last commit.

Updating with upstream changes

When Evennia’s official repository updates, first make sure to commit all your changes to your branch and then checkout the “clean” master branch: git commit--all git checkout master

Pull the latest changes from upstream: git pull upstream master

This should sync your local master branch with upstream Evennia’s master branch (where our development happens). Now we go back to our own work-branch and merge the updated master into our branch. git checkout mygame git merge master

If everything went well, your mygame branch will now have the latest version of Evennia. To update also your myfixes branch just do git checkout myfixes git merge master

Use git log to see what has changed. You may need to restart the server or run manage.py migrate if the database schema changed (this will be seen in the commit log and on the mailing list). See the Git manuals for learning more about useful day-to-day commands, and special situations such as dealing with merge collisions.

12.3.4 Sharing your Code Publicly

Up to this point your mygame and myfixes branches only exist on your local computer. No one else can see them. If you want a copy of those branches to also appear in your online fork on github, make sure to have checked out your “mygame” branch and then run the following: git push-u mygame

This will create a new remote branch named “mygame” in your online repository. Henceforth you can just use git push from your mygame branch to push your changes online. This is a great way to keep your source backed-up and accessible. Remember though that unless you have paid for a “private” repository at Github everyone will be able to browse and download your code (same way as you can with Evennia itself).

12.3.5 Committing fixes to Evennia

Let’s say you found a bug in Evennia and want to contribute with a fix. We will assume you have set up your local repository as outlined in the previous sections. We will assume you do your fixing in the “myfixes” branch (but you might as well consider having a new branch named appropriately for every feature you want to contribute). First commit any changes you may have made elsewhere and then update the master branch and your “myfixes” branch to the latest evennia version:

234 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

git checkout master git pull upstream git checkout myfixes git merge master

Now you fix and test things in your “myfixes” branch, committing as you go: git commit--all-m"This fixes issue #124."

Make sure to always make clear and descriptive commit messages so it’s easy to see what you intended. If you implement multiple separate features/bug-fixes, split them into separate commits. You can do any number of commits as you work. Once you are at a stage where you want to show the world what you did, push all the so-far committed changes to your online clone, in a new remote branch: git push-u myfixes

This only needs to be done once. If you already created the remote branch earlier, just stand in your “myfixes” branch and do git push. You next need to tell the Evennia developers that they should merge your brilliant changes into Evennia proper. Create a pull request and follow the instructions. Make sure to specifically select your myfixes branch to be the source of the merge. Evennia developers will then be able to examine your request and merge it if it is deemed suitable.

12.3.6 Sharing your Code Privately

Creating a publicly visible online clone might not be what you want for all parts of your development process - you may prefer a more private venue when sharing your revolutionary work with your team.

GitHub offers private repositories at a cost. Alternatively you could host your code on BitBucket, which offers free private repositories as long as your development team is not too big. ‘ ‘__

Version control software allows you to track the changes you make to your code, as well as being able to easily backtrack these changes, share your development efforts and more. Even if you are not contributing to Evennia itself, and only wish to develop your own MU* using Evennia, having a version control system in place is a good idea (and standard coding practice). For an introduction to the concept, start with the Wikipedia article here. Evennia uses the version control system Git and this is what will be covered henceforth. Note that this page also deals with commands for Linux operating systems, and the steps below may vary for other systems, however where possible links will be provided for alternative instructions. For more help on using Git, please refer to the Official GitHub documentation.

12.3.7 Setting up Git

If you have gotten Evennia installed, you will have Git already and can skip to Step 2 below. Otherwise you will need to install Git on your platform. You can find expanded instructions for installation here.

Step 1: Install Git

• Fedora Linux

12.3. Version Control 235 Evennia Documentation, Release 0.9

yum install git-core

• Debian Linux (Ubuntu, Linux Mint, etc.)

apt-get install git

• Windows: It is recommended to use Git for Windows. • Mac: Mac platforms offer two methods for installation, one via MacPorts, which you can find out about here, or you can use the Git OSX Installer.

Step 2: Define user/e-mail Settings for Git

To avoid a common issue later, you will need to set a couple of settings; first you will need to tell Git your username, followed by your e-mail address, so that when you commit code later you will be properly credited. Note that your commit information will be visible to everyone if you ever contribute to Evennia or use an online service like github to host your code. So if you are not comfortable with using your real, full name online, put a nickname here. 1. Set the default name for git to use when you commit:

git config-- global user.name"Your Name Here"

2. Set the default email for git to use when you commit:

git config-- global user.email"[email protected]"

12.3.8 Putting your game folder under version control

Note: The game folder’s version control is completely separate from Evennia’s repository. After you have set up your game you will have created a new folder to host your particular game (let’s call this folder mygame for now). This folder is not under version control at this point. git init mygame

Your mygame folder is now ready for version control! Now add all the content and make a first commit: cd mygame git add * git commit-m"Initial commit"

Read on for help on what these commands do.

Tracking files

When working on your code or fix bugs in your local branches you may end up creating new files. If you do you must tell Git to track them by using the add command: git add

236 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

You can check the current status of version control with git status. This will show if you have any modified, added or otherwise changed files. Some files, like database files, logs and temporary PID files are usually not tracked in version control. These should either not show up or have a question mark in front of them.

Controlling tracking

You will notice that some files are not covered by your git version control, notably your settings file (mygame/ server/conf/settings.py) and your sqlite3 database file mygame/server/evennia.db3. This is con- trolled by the hidden file mygame/.gitignore. Evennia creates this file as part of the creation of your game directory. Everything matched in this file will be ignored by GIT. If you want to, for example, include your settings file for collaborators to access, remove that entry in .gitignore. Note: You should never put your sqlite3 database file into git by removing its entry in .gitignore. GIT is for backing up your code, not your database. That way lies madness and a good chance you’ll confuse yourself so that after a few commits and reverts don’t know what is in your database or not. If you want to backup your database, do so by simply copying the file on your hard drive to a backup-name.

Committing your Code

Committing means storing the current snapshot of your code within git. This creates a “save point” or “history” of your development process. You can later jump back and forth in your history, for example to figure out just when a bug was introduced or see what results the code used to produce compared to now. It’s usually a good idea to commit your changes often. Committing is fast and local only - you will never commit anything online at this point. To commit your changes, use git commit--all

This will save all changes you made since last commit. The command will open a text editor where you can add a message detailing the changes you’ve made. Make it brief but informative. You can see the history of commits with git log. If you don’t want to use the editor you can set the message directly by using the -m flag: git commit--all-m"This fixes a bug in the combat code."

Changing your mind

If you have non-committed changes that you realize you want to throw away, you can do the following: git checkout

This will revert the file to the state it was in at your last commit, throwing away the changes you did to it since. It’s a good way to make wild experiments without having to remember just what you changed. If you do git checkout . you will throw away all changes since the last commit.

Pushing your code online

So far your code is only located on your private machine. A good idea is to back it up online. The easiest way to do this is to push it to your own remote repository on GitHub. 1. Make sure you have your game directory setup under git version control as described above. Make sure to commit any changes. 2. Create a new, empty repository on Github. Github explains how here (do not “Initialize the repository with a README” or else you’ll create unrelated histories).

12.3. Version Control 237 Evennia Documentation, Release 0.9

3. From your local game dir, do git remote add origin where is the URL to your online repo. This tells your game dir that it should be pushing to the remote online dir. 4. git remote -v to verify the online dir. 5. git push origin master now pushes your game dir online so you can see it on github.com. You can commit your work locally (git commit --all -m "Make a change that ...") as many times as you want. When you want to push those changes to your online repo, you do git push. You can also git clone from your online repo to somewhere else (like your production server) and henceforth do git pull to update that to the latest thing you pushed. Note that GitHub’s repos are, by default publicly visible by all. Creating a publicly visible online clone might not be what you want for all parts of your development process - you may prefer a more private venue when sharing your revolutionary work with your team. If that’s the case you can change your repository to “Private” in the github settings. Then your code will only be visible to those you specifically grant access.

12.3.9 Forking Evennia

This helps you set up an online fork of Evennia so you can easily commit fixes and help with upstream development.

Step 1: Fork the evennia/master repository

Before proceeding with the following step, make sure you have registered and created an account on GitHub.com. This is necessary in order to create a fork of Evennia’s master repository, and to push your commits to your fork either for yourself or for contributing to Evennia. A fork is a clone of the master repository that you can make your own commits and changes to. At the top of this page, click the “Fork” button, as it appears below.

Step 2: Clone your fork

The fork only exists online as of yet. In a terminal, change your directory to the folder you wish to develop in. From this directory run the following command:

git clone https://github.com/yourusername/evennia.git

This will download your fork to your computer. It creates a new folder evennia/ at your current location.

Step 3: Configure remotes

A remote is a repository stored on another computer, in this case on GitHub’s server. When a repository is cloned, it has a default remote called origin. This points to your fork on GitHub, not the original repository it was forked from. To easily keep track of the original repository (that is, Evennia’s official repository), you need to add another remote. The standard name for this remote is “upstream”. Below we change the active directory to the newly cloned “evennia” directory and then assign the original Evennia repository to a remote called “upstream”:

238 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

cd evennia git remote add upstream https://github.com/evennia/evennia.git

If you also want to access Evennia’s develop branch (the bleeding edge development branch) do the following:

git fetch upstream develop git checkout develop

You should now have the upstream branch available locally. You can use this instead of master below if you are contributing new features rather than bug fixes.

12.3.10 Working with your fork

A branch is a separate instance of your code. Changes you do to code in a branch does not affect that in other branches (so if you for example add/commit a file to one branch and then switches to another branch, that file will be gone until you switch back to the first branch again). One can switch between branches at will and create as many branches as one needs for a given project. The content of branches can also be merged together or deleted without affecting other branches. This is not only a common way to organize development but also to test features without messing with existing code. The default branch of git is called the “master” branch. As a rule of thumb, you should never make modifications directly to your local copy of the master branch. Rather keep the master clean and only update it by pulling our latest changes to it. Any work you do should instead happen in a local, other branches.

Making a work branch

git checkout-b myfixes

This command will checkout and automatically create the new branch myfixes on your machine. If you stared out in the master branch, myfixes will be a perfect copy of the master branch. You can see which branch you are on with git branch and change between different branches with git checkout . Branches are fast and cheap to create and manage. It is common practice to create a new branch for every bug you want to work on or feature you want to create, then create a pull request for that branch to be merged upstream (see below). Not only will this organize your work, it will also make sure that your master branch version of Evennia is always exactly in sync with the upstream version’s master branch.

Updating with upstream changes

When Evennia’s official repository updates, first make sure to commit all your changes to your branch and then checkout the “clean” master branch:

git commit--all git checkout master

Pull the latest changes from upstream:

git pull upstream master

This should sync your local master branch with upstream Evennia’s master branch. Now we go back to our own work-branch (let’s say it’s still called “myfixes”) and merge the updated master into our branch.

12.3. Version Control 239 Evennia Documentation, Release 0.9

git checkout myfixes git merge master

If everything went well, your myfixes branch will now have the latest version of Evennia merged with whatever changes you have done. Use git log to see what has changed. You may need to restart the server or run manage. py migrate if the database schema changed (this will be seen in the commit log and on the mailing list). See the Git manuals for learning more about useful day-to-day commands, and special situations such as dealing with merge collisions.

12.3.11 Sharing your Code Publicly

Up to this point your myfixes branch only exists on your local computer. No one else can see it. If you want a copy of this branch to also appear in your online fork on GitHub, make sure to have checked out your “myfixes” branch and then run the following:

git push-u origin myfixes

This will create a new remote branch named “myfixes” in your online repository (which is refered to as “origin” by default); the -u flag makes sure to set this to the default push location. Henceforth you can just use git push from your myfixes branch to push your changes online. This is a great way to keep your source backed-up and accessible. Remember though that by default your repository will be public so everyone will be able to browse and download your code (same way as you can with Evennia itself). If you want secrecy you can change your repository to “Private” in the Github settings. Note though that if you do, you might have trouble contributing to Evennia (since we can’t see the code you want to share). Note: If you hadn’t setup a public key on GitHub or aren’t asked for a username/password, you might get an error ‘‘403: Forbidden Access‘‘ at this stage. In that case, some users have reported that the workaround is to create a file ‘‘.netrc‘‘ under your home directory and add your credentials there:

machine github.com login password

12.3.12 Committing fixes to Evennia

Contributing can mean both bug-fixes or adding new features to Evennia. Please note that if your change is not already listed and accepted in the Issue Tracker, it is recommended that you first hit the developer mailing list or IRC chat to see beforehand if your feature is deemed suitable to include as a core feature in the engine. When it comes to bug-fixes, other developers may also have good input on how to go about resolving the issue. To contribute you need to have forked Evennia first. As described above you should do your modification in a separate local branch (not in the master branch). This branch is what you then present to us (as a Pull request, PR, see below). We can then merge your change into the upstream master and you then do git pull to update master usual. Now that the master is updated with your fixes, you can safely delete your local work branch. Below we describe this work flow. First update the Evennia master branch to the latest Evennia version:

git checkout master git pull upstream master

Next, create a new branch to hold your contribution. Let’s call it the “fixing_strange_bug” branch:

240 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

git checkout-b fixing_strange_bug

It is wise to make separate branches for every fix or series of fixes you want to contribute. You are now in your new fixing_strange_bug branch. You can list all branches with git branch and jump between branches with git checkout . Code and test things in here, committing as you go: git commit--all-m"Fix strange bug in look command. Resolves #123."

You can make multiple commits if you want, depending on your work flow and progress. Make sure to always make clear and descriptive commit messages so it’s easy to see what you intended. To refer to, say, issue number 123, write #123, it will turn to a link on GitHub. If you include the text “Resolves #123”, that issue will be auto-closed on GitHub if your commit gets merged into main Evennia. If you refer to in-game commands that start with @(such as @examine), please put them in backticks ‘, for example ‘@examine‘. The reason for this is that GitHub uses @username to refer to GitHub users, so if you forget the ticks, any user happening to be named examine will get a notification . . . . If you implement multiple separate features/bug-fixes, split them into different branches if they are very different and should be handled as separate PRs. You can do any number of commits to your branch as you work. Once you are at a stage where you want to show the world what you did you might want to consider making it clean for merging into Evennia’s master branch by using git rebase (this is not always necessary, and if it sounds too hard, say so and we’ll handle it on our end). Once you are ready, push your work to your online Evennia fork on github, in a new remote branch: git push-u origin fixing_strange_bug

The -u flag is only needed the first time - this tells GIT to create a remote branch. If you already created the remote branch earlier, just stand in your fixing_strange_bug branch and do git push. Now you should tell the Evennia developers that they should consider merging your brilliant changes into Evennia proper. Create a pull request and follow the instructions. Make sure to specifically select your fixing_strange_bug branch to be the source of the merge. Evennia developers will then be able to examine your request and merge it if it’s deemed suitable. Once your changes have been merged into Evennia your local fixing_strange_bug can be deleted (since your changes are now available in the “clean” Evennia repository). Do git branch-D fixing_strange_bug to delete your work branch. Update your master branch (checkout master and then git pull) and you should get your fix back, now as a part of official Evennia!

12.3.13 GIT tips and tricks

Some of the GIT commands can feel a little long and clunky if you need to do them often. Luckily you can create aliases for those. Here are some useful commands to run:

# git st # - view brief status info git config-- global alias.st'status -s'

Above, you only need to ever enter the git config ... command once - you have then added the new alias. Afterwards, just do git st to get status info. All the examples below follow the same template.

12.3. Version Control 241 Evennia Documentation, Release 0.9

# git cl # - clone a repository git config-- global alias.cl clone

# git cma "commit message" # - commit all changes without opening editor for message git config-- global alias.cma'commit -a -m'

# git ca # - amend text to your latest commit message git config-- global alias.ca'commit --amend'

# git fl # - file log; shows diffs of files in latest commits git config-- global alias.fl'log -u'

# git co [branchname] # - checkout git config-- global alias.co checkout

# git br # - create branch git config-- global alias.br branch

# git ls # - view log tree git config-- global alias.ls'log --pretty=format:"%C(green)%h\%C(yellow)[ %ad]%Cred

˓→%d\%Creset %s%Cblue\[ %cn]" --decorate --date=relative --graph'

# git diff # - show current uncommitted changes git config-- global alias.diff'diff --word-diff'

# git grep # - search (grep) codebase for a search criterion git config-- global alias.grep'grep -Ii'

To get a further feel for GIT there is also a good YouTube talk about it - it’s a bit long but it will help you understand the underlying ideas behind GIT (which in turn makes it a lot more intuitive to use).

12.4 Profiling

This is considered an advanced topic mainly of interest to server developers.

12.4.1 Introduction

Sometimes it can be useful to try to determine just how efficient a particular piece of code is, or to figure out if one could speed up things more than they are. There are many ways to test the

242 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

performance of Python and the running server.

Before digging into this section, remember Donald Knuth’s words of wisdom: [. . . ]about 97% of the time: Premature optimization is the root of all evil.

That is, don’t start to try to optimize your code until you have actually identified a need to do so. This means your code must actually be working before you start to consider optimization. Optimization will also often make your code more complex and harder to read. Consider readability and maintainability and you may find that a small gain in speed is just not worth it.

12.4.2 Simple timer tests

Python’s timeit module is very good for testing small things. For example, in order to test if it is faster to use a for loop or a list comprehension you could use the following code:

import timeit # Time to do 1000000 for loops timeit.timeit("for i in range(100):\n a.append(i)", setup="a = []") <<< 10.70982813835144 # Time to do 1000000 list comprehensions timeit.timeit("a = [i for i in range(100)]") <<< 5.358283996582031

The setup keyword is used to set up things that should not be included in the time measurement, like a = [] in the first call. By default the timeit function will re-run the given test 1000000 times and returns the total time to do so (so not the average per test). A hint is to not use this default for testing something that includes database writes - for that you may want to use a lower number of repeats (say 100 or 1000) using the number=100 keyword.

12.4.3 Using cProfile

Python comes with its own profiler, named cProfile (this is for cPython, no tests have been done with pypy at this point). Due to the way Evennia’s processes are handled, there is no point in using the normal way to start the profiler (python -m cProfile evennia.py). Instead you start the profiler through the launcher:

evennia--profiler start

This will start Evennia with the Server component running (in daemon mode) under cProfile. You could instead try --profile with the portal argument to profile the Portal (you would then need to start the Server separately). Please note that while the profiler is running, your process will use a lot more memory than usual. Memory usage is even likely to climb over time. So don’t leave it running perpetually but monitor it carefully (for example using the top command on Linux or the Task Manager’s memory display on Windows). Once you have run the server for a while, you need to stop it so the profiler can give its report. Do not kill the program from your task manager or by sending it a kill signal - this will most likely also mess with the profiler. Instead either use evennia.py stop or (which may be even better), use @shutdown from inside the game. Once the server has fully shut down (this may be a lot slower than usual) you will find that profiler has created a new file mygame/server/logs/server.prof.

12.4. Profiling 243 Evennia Documentation, Release 0.9

12.4.4 Analyzing the profile

The server.prof file is a binary file. There are many ways to analyze and display its contents, all of which has only been tested in Linux (If you are a Windows/Mac user, let us know what works).

We recommend the Runsnake visualizer to see the processor usage of different processes in a graphical form. For more detailed listing of usage time, you can use KCachegrind. To make KCachegrind work with Python profiles you also need the wrapper script pyprof2calltree. You can get pyprof2calltree via pip whereas KCacheGrind is something you need to get via your package manager or their homepage.

How to analyze and interpret profiling data is not a trivial issue and depends on what you are profiling for. Evennia being an asynchronous server can also confuse profiling. Ask on the mailing list if you need help and be ready to be able to supply your server.prof file for comparison, along with the exact conditions under which it was obtained.

12.4.5 The Dummyrunner

It is difficult to test “actual” game performance without having players in your game. For this reason Evennia comes with the Dummyrunner system. The Dummyrunner is a stress-testing system: a separate program that logs into your game with simulated players (aka “bots” or “dummies”). Once connected these dummies will semi-randomly perform various tasks from a list of possible actions. Use Ctrl-C to stop the Dummyrunner. Warning: You should not run the Dummyrunner on a production database. It will spawn many objects and also needs to run with general permissions. To launch the Dummyrunner, first start your server normally (with or without profiling, as above). Then start a new terminal/console window and active your virtualenv there too. In the new terminal, try to connect 10 dummy players:

evennia--dummyrunner 10

The first time you do this you will most likely get a warning from Dummyrunner. It will tell you to copy an import string to the end of your settings file. Quit the Dummyrunner (Ctrl-C) and follow the instructions. Restart Evennia and try evennia --dummyrunner 10 again. Make sure to remove that extra settings line when running a public server. The actions perform by the dummies is controlled by a settings file. The default Dummyrunner settings file is evennia/server/server/profiling/dummyrunner_settings.py but you shouldn’t modify this di- rectly. Rather create/copy the default file to mygame/server/conf/ and modify it there. To make sure to use your file over the default, add the following line to your settings file:

DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py"

Hint: Don’t start with too many dummies. The Dummyrunner defaults to taxing the server much more intensely than an equal number of human players. A good dummy number to start with is 10-100.

Once you have the dummyrunner running, stop it with Ctrl-C.

Generally, the dummyrunner system makes for a decent test of general performance; but it is of course hard to actually mimic human user behavior. For this, actual real-game testing is required.

244 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

12.5 Unit Testing

Unit testing means testing components of a program in isolation from each other to make sure every part works on its own before using it with others. Extensive testing helps avoid new updates causing unexpected side effects as well as alleviates general code rot (a more comprehensive wikipedia article on unit testing can be found here). A typical unit test set calls some function or method with a given input, looks at the result and makes sure that this result looks as expected. Rather than having lots of stand-alone test programs, Evennia makes use of a central test runner. This is a program that gathers all available tests all over the Evennia source code (called test suites) and runs them all in one go. Errors and tracebacks are reported. By default Evennia only tests itself. But you can also add your own tests to your game code and have Evennia run those for you.

12.5.1 Running the Evennia test suite

To run the full Evennia test suite, go to your game folder and issue the command

evennia test evennia

This will run all the evennia tests using the default settings. You could also run only a subset of all tests by specifying a subpackage of the library:

evennia test evennia.commands.default

A temporary database will be instantiated to manage the tests. If everything works out you will see how many tests were run and how long it took. If something went wrong you will get error messages. If you contribute to Evennia, this is a useful sanity check to see you haven’t introduced an unexpected bug.

12.5.2 Running tests with custom settings file

If you have implemented your own tests for your game (see below) you can run them from your game dir with

evennia test.

The period (.) means to run all tests found in the current directory and all subdirectories. You could also specify, say, typeclasses or world if you wanted to just run tests in those subdirs. Those tests will all be run using the default settings. To run the tests with your own settings file you must use the --settings option:

evennia test--settings settings.py.

The --settings option of Evennia takes a file name in the mygame/server/conf folder. It is normally used to swap settings files for testing and development. In combination with test, it forces Evennia to use this settings file over the default one.

12.5.3 Writing new tests

Evennia’s test suite makes use of Django unit test system, which in turn relies on Python’s unittest module. If you want to help out writing unittests for Evennia, take a look at Evennia’s coveralls.io page. There you see which modules have any form of test coverage and which does not.

12.5. Unit Testing 245 Evennia Documentation, Release 0.9

To make the test runner find the tests, they must be put in a module named test*.py (so test.py, tests.py etc). Such a test module will be found wherever it is in the package. It can be a good idea to look at some of Evennia’s tests.py modules to see how they look. Inside a testing file, a unittest.TestCase class is used to test a single aspect or component in various ways. Each test case contains one ore more test methods - these define the actual tests to run. You can name the test methods anything you want as long as the name starts with “test_”. Your TestCase class can also have a method setUp(). This is run before each test, setting up and storing whatever preparations the test methods need. Conversely, a tearDown() method can optionally do cleanup after each test. To test the results, you use special methods of the TestCase class. Many of those start with “assert”, such as assertEqual or assertTrue. Example of a TestCase class:

import unittest

# the function we want to test from mypath import myfunc

class TestObj(unittest.TestCase): "This tests a function myfunc."

def test_return_value(self): "test method. Makes sure return value is as expected." expected_return="This is me being nice." actual_return= myfunc() # test self.assertEqual(expected_return, actual_return) def test_alternative_call(self): "test method. Calls with a keyword argument." expected_return="This is me being baaaad." actual_return= myfunc(bad= True) # test self.assertEqual(expected_return, actual_return)

You might also want to read the documentation for the unittest module.

Using the EvenniaTest class

Evennia offers a custom TestCase, the evennia.utils.test_resources.EvenniaTest class. This class initiates a range of useful properties on themselves for testing Evennia systems. Examples are .account and . session representing a mock connected Account and its Session and .char1 and char2 representing Char- acters complete with a location in the test database. These are all useful when testing Evennia system requir- ing any of the default Evennia typeclasses as inputs. See the full definition of the EvenniaTest class in even- nia/utils/test_resources.py.

# in a test module

from evennia.utils.test_resources import EvenniaTest class TestObject(EvenniaTest): def test_object_search(self): # char1 and char2 are both created in room1 self.assertEqual(self.char1.search(self.char2.key), self.char2) self.assertEqual(self.char1.search(self.char1.location.key), self.char1.

˓→location) (continues on next page)

246 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

(continued from previous page) # ...

Testing in-game Commands

In-game Commands are a special case. Tests for the default commands are put in evennia/commands/default/ tests.py. This uses a custom CommandTest class that inherits from evennia.utils.test_resources. EvenniaTest described above. CommandTest supplies extra convenience functions for executing commands and check that their return values (calls of msg() returns expected values. It uses Characters and Sessions generated on the EvenniaTest class to call each class). Each command tested should have its own TestCase class. Inherit this class from the CommandTest class in the same module to get access to the command-specific utilities mentioned.

from evennia.commands.default.tests import CommandTest from evennia.commands.default import general class TestSet(CommandTest): "tests the look command by simple call, using Char2 as a target" def test_mycmd_char(self): self.call(general.CmdLook(),"Char2","Char2(#7)") "tests the look command by simple call, with target as room" def test_mycmd_room(self): self.call(general.CmdLook(),"Room", "Room(#1)\nroom_desc\nExits: out(#3)\n" "You see: Obj(#4), Obj2(#5), Char2(#7)")

Unit testing contribs with custom models

A special case is if you were to create a contribution to go to the evennia/contrib folder that uses its own database models. The problem with this is that Evennia (and Django) will only recognize models in settings. INSTALLED_APPS. If a user wants to use your contrib, they will be required to add your models to their settings file. But since contribs are optional you cannot add the model to Evennia’s central settings_default.py file - this would always create your optional models regardless of if the user wants them. But at the same time a contribution is a part of the Evennia distribution and its unit tests should be run with all other Evennia tests using evennia test evennia. The way to do this is to only temporarily add your models to the INSTALLED_APPS directory when the test runs. here is an example of how to do it. Note that this solution, derived from this stackexchange answer is currently untested! Please report your findings.

# a file contrib/mycontrib/tests.py

from django.conf import settings import django from evennia.utils.test_resources import EvenniaTest

OLD_DEFAULT_SETTINGS= settings.INSTALLED_APPS DEFAULT_SETTINGS= dict( INSTALLED_APPS=( 'contrib.mycontrib.tests', ), DATABASES={ (continues on next page)

12.5. Unit Testing 247 Evennia Documentation, Release 0.9

(continued from previous page) "default":{ "ENGINE":"django.db.backends.sqlite3" } }, SILENCED_SYSTEM_CHECKS=["1_7.W001"], ) class TestMyModel(EvenniaTest): def setUp(self):

if not settings.configured: settings.configure(**DEFAULT_SETTINGS) django.setup()

from django.core.management import call_command from django.db.models import loading loading.cache.loaded= False call_command('syncdb', verbosity=0)

def tearDown(self): settings.configure(**OLD_DEFAULT_SETTINGS) django.setup()

from django.core.management import call_command from django.db.models import loading loading.cache.loaded= False call_command('syncdb', verbosity=0)

# test cases below ...

def test_case(self): # test case here

A note on adding new tests

Having an extensive tests suite is very important for avoiding code degradation as Evennia is developed. Only a small fraction of the Evennia codebase is covered by test suites at this point. Writing new tests is not hard, it’s more a matter of finding the time to do so. So adding new tests is really an area where everyone can contribute, also with only limited Python skills.

A note on making the test runner faster

If you have custom models with a large number of migrations, creating the test database can take a very long time. If you don’t require migrations to run for your tests, you can disable them with the django-test-without-migrations package. To install it, simply:

$ pip install django-test-without-migrations

Then add it to your INSTALLED_APPS in your server.conf.settings.py:

INSTALLED_APPS=( # ... 'test_without_migrations', )

248 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

After doing so, you can then run tests without migrations by adding the --nomigrations argument:

evennia test--settings settings.py--nomigrations.

12.5.4 Testing for Game development (mini-tutorial)

Unit testing can be of paramount importance to game developers. When starting with a new game, it is recommended to look into unit testing as soon as possible; an already huge game is much harder to write tests for. The benefits of testing a game aren’t different from the ones regarding library testing. For example it is easy to introduce bugs that affect previously working code. Testing is there to ensure your project behaves the way it should and continue to do so. If you have never used unit testing (with Python or another language), you might want to check the official Python documentation about unit testing, particularly the first section dedicated to a basic example.

Basic testing using Evennia

Evennia’s test runner can be used to launch tests in your game directory (let’s call it ‘mygame’). Evennia’s test runner does a few useful things beyond the normal Python unittest module: • It creates and sets up an empty database, with some useful objects (accounts, characters and rooms, among others). • It provides simple ways to test commands, which can be somewhat tricky at times, if not tested properly. Therefore, you should use the command-line to execute the test runner, while specifying your own game directories (not the one containing evennia). Go to your game directory (referred as ‘mygame’ in this section) and execute the test runner:

evennia--settings settings.py test commands

This command will execute Evennia’s test runner using your own settings file. It will set up a dummy database of your choice and look into the ‘commands’ package defined in your game directory (mygame/commands in this example) to find tests. The test module’s name should begin with ‘test’ and contain one or more TestCase. A full example can be found below.

A simple example

In your game directory, go to commands and create a new file tests.py inside (it could be named anything starting with test). We will start by making a test that has nothing to do with Commands, just to show how unit testing works:

# mygame/commands/tests.py

import unittest

class TestString(unittest.TestCase):

"""Unittest for strings (just a basic example)."""

def test_upper(self): """Test the upper() str method.""" self.assertEqual('foo'.upper(),'FOO')

12.5. Unit Testing 249 Evennia Documentation, Release 0.9

This example, inspired from the Python documentation, is used to test the ‘upper()’ method of the ‘str’ class. Not very useful, but it should give you a basic idea of how tests are used. Let’s execute that test to see if it works.

> evennia--settings settings.py test commands

TESTING: Using specified settings file'server.conf.settings'.

(Obs: Evennia's full test suite may not pass if the settings are very different from the default. Use'test .' as arguments to run only tests on the game dir.)

Creating test database for alias'default'... . ------Ran1 test in 0.001s

OK Destroying test database for alias'default'...

We specified the commands package to the evennia test command since that’s where we put our test file. In this case we could just as well just said . to search all of mygame for testing files. If we have a lot of tests it may be useful to test only a single set at a time though. We get an information text telling us we are using our custom settings file (instead of Evennia’s default file) and then the test runs. The test passes! Change the “FOO” string to something else in the test to see how it looks when it fails.

Testing commands

This section will test the proper execution of the ‘abilities’ command, as described in the First Steps Coding page. Follow this tutorial to create the ‘abilities’ command, we will need it to test it. Testing commands in Evennia is a bit more complex than the simple testing example we have seen. Luckily, Evennia supplies a special test class to do just that . . . we just need to inherit from it and use it properly. This class is called ‘CommandTest’ and is defined in the ‘evennia.commands.default.tests’ package. To create a test for our ‘abilities’ command, we just need to create a class that inherits from ‘CommandTest’ and add methods. We could create a new test file for this but for now we just append to the tests.py file we already have in commands from before.

# bottom of mygame/commands/tests.py

from evennia.commands.default.tests import CommandTest

from commands.command import CmdAbilities from typeclasses.characters import Character class TestAbilities(CommandTest):

character_typeclass= Character

def test_simple(self): self.call(CmdAbilities(),"","STR: 5, AGI: 4, MAG: 2")

• Line 1-4: we do some importing. ‘CommandTest’ is going to be our base class for our test, so we need it. We also import our command (‘CmdAbilities’ in this case). Finally we import the ‘Character’ typeclass. We need it, since ‘CommandTest’ doesn’t use ‘Character’, but ‘DefaultCharacter’, which means the character calling the command won’t have the abilities we have written in the ‘Character’ typeclass.

250 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

• Line 6-8: that’s the body of our test. Here, a single command is tested in an entire class. Default commands are usually grouped by category in a single class. There is no rule, as long as you know where you put your tests. Note that we set the ‘character_typeclass’ class attribute to Character. As explained above, if you didn’t do that, the system would create a ‘DefaultCharacter’ object, not a ‘Character’. You can try to remove line 4 and 8 to see what happens when running the test. • Line 10-11: our unique testing method. Note its name: it should begin by ‘test_’. Apart from that, the method is quite simple: it’s an instance method (so it takes the ‘self’ argument) but no other arguments are needed. Line 11 uses the ‘call’ method, which is defined in ‘CommandTest’. It’s a useful method that compares a command against an expected result. It would be like comparing two strings with ‘assertEqual’, but the ‘call’ method does more things, including testing the command in a realistic way (calling its hooks in the right order, so you don’t have to worry about that). Line 11 can be understood as: test the ‘abilities’ command (first parameter), with no argument (second parameter), and check that the character using it receives his/her abilities (third parameter). Let’s run our new test:

> evennia--settings settings.py test commands [...] Creating test database for alias'default'... .. ------Ran2 tests in 0.156s

OK Destroying test database for alias'default'...

Two tests were executed, since we have kept ‘TestString’ from last time. In case of failure, you will get much more information to help you fix the bug.

12.6 Setting up PyCharm

# Directions for setting up PyCharm with Evennia PyCharm is a Python developer’s IDE from Jetbrains available for Windows, Mac and Linux. It is a commercial prod- uct but offer free trials, a scaled-down community edition and also generous licenses for OSS projects like Evennia. This page was originally tested on Windows (so use Windows-style path examples), but should work the same for all platforms. First, install Evennia on your local machine with [[Getting Started]]. If you’re new to PyCharm, loading your project is as easy as selecting the Open option when PyCharm starts, and browsing to your game folder (the one created with evennia --init). We refer to it as mygame here. If you want to be able to examine evennia’s core code or the scripts inside your virtualenv, you’ll need to add them to your project too: 1. Go to File > Open... 2. Select the folder (i.e. the evennia root) 3. Select “Open in current window” and “Add to currently opened projects”

12.6. Setting up PyCharm 251 Evennia Documentation, Release 0.9

12.6.1 Setting up the project interpreter

It’s a good idea to do this before attempting anything further. The rest of this page assumes your project is already configured in PyCharm. 1. Go to File > Settings... > Project: \ > Project Interpreter 2. Click the Gear symbol > Add local 3. Navigate to your evenv/scripts directory, and select Python.exe Enjoy seeing all your imports checked properly, setting breakpoints, and live variable watching!

12.6.2 Attaching PyCharm debugger to Evennia

1. Launch Evennia in your preferred way (usually from a console/terminal) 2. Open your project in PyCharm 3. In the PyCharm menu, select Run > Attach to Local Process... 4. From the list, pick the twistd process with the server.py parameter (Example: twistd.exe --nodaemon --logfile=\\server\logs\server.log --python=\\evennia\server\server.py) Of course you can attach to the portal process as well. If you want to debug the Evennia launcher or runner for some reason (or just learn how they work!), see Run Configuration below. NOTE: Whenever you reload Evennia, the old Server process will die and a new one start. So when you restart you have to detach from the old and then reattach to the new process that was created.

To make the process less tedious you can apply a filter in settings to show only the server.py process in the list. To do that navigate to: Settings/Preferences | Build, Execution, Deployment | Python Debugger and then in Attach to process field put in: twistd.exe" --nodaemon. This is an example for windows, I don’t have a working mac/linux box.

252 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

12.6.3 Setting up an Evennia run configuration

This configuration allows you to launch Evennia from inside PyCharm. Besides convenience, it also allows suspending and debugging the evennia_launcher or evennia_runner at points earlier than you could by running them externally and attaching. In fact by the time the server and/or portal are running the launcher will have exited already. 1. Go to Run > Edit Configutations... 2. Click the plus-symbol to add a new configuration and choose Python 3. Add the script: \\evenv\Scripts\evennia_launcher.py (substitute your virtualenv if it’s not named evenv) 4. Set script parameters to: start -l (-l enables console logging) 5. Ensure the chosen interpreter is from your virtualenv 6. Set Working directory to your mygame folder (not evenv nor evennia) 7. You can refer to the PyCharm documentation for general info, but you’ll want to set at least a config name (like “MyMUD start” or similar). Now set up a “stop” configuration by following the same steps as above, but set your Script parameters to: stop (and name the configuration appropriately). A dropdown box holding your new configurations should appear next to your PyCharm run button. Select MyMUD start and press the debug icon to begin debugging. Depending on how far you let the program run, you may need to run your “MyMUD stop” config to actually stop the server, before you’ll be able start it again.

12.6. Setting up PyCharm 253 Evennia Documentation, Release 0.9

12.6.4 Alternative run configuration - utilizing logfiles as source of data

This configuration takes a bit different approach as instead of focusing on getting the data back through logfiles. Reason for that is this way you can easily separate data streams, for example you rarely want to follow both server and portal at the same time, and this will allow it. This will also make sure to stop the evennia before starting it, essentially working as reload command (it will also include instructions how to disable that part of functionality). We will start by defining a configuration that will stop evennia. This assumes that upfire is your pycharm project name, and also the game name, hence the upfire/upfire path. 1. Go to Run > Edit Configutations...\ 2. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should be project default) 3. Name the configuration as “stop evennia” and fill rest of the fields accordingly to the image:

4. Press Apply Now we will define the start/reload command that will make sure that evennia is not running already, and then start the server in one go. 1. Go to Run > Edit Configutations...\ 2. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should be project default) 3. Name the configuration as “start evennia” and fill rest of the fields accordingly to the image:

254 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

4. Navigate to the Logs tab and add the log files you would like to follow. The pic- ture shows adding portal.log which will show itself in portal tab when running:

12.6. Setting up PyCharm 255 Evennia Documentation, Release 0.9

5. Skip the following steps if you don’t want the launcher to stop evennia before starting. 6. Head back to Configuration tab and press the + sign at the bottom, under Before launch.... and select Run another configuration from the submenu that will pop up. 7. Click stop evennia and make sure that it’s added to the list like on the image above. 8. Click Apply and close the run configuration window.

You are now ready to go, and if you will fire up start evennia configuration you should see following in the bottom panel:

256 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

and you can click through the tabs to check appropriate logs, or even the console output as it is still running in interactive mode.

12.7 Using Travis

Evennia uses Travis CI to check that it’s building successfully after every commit to its Github repository (you can for example see the build: passing badge at the top of Evennia’s Readme file). If your game is open source on Github you may also use Travis for free. See the Travis docs for how to get started. After logging in you will get to point Travis to your repository on github. One further thing you need to set up yourself is a Travis config file named .travis.yml (note the initial period .). This should be created in the root of your game directory. The idea with this file is that it describes what Travis needs to import and build in order to create an instance of Evennia from scratch and then run validation tests on it. Here is an example:

language: python python: - "2.7" install: - git clone https://github.com/evennia/evennia.git - cd evennia - pip install -e . - cd $TRAVIS_BUILD_DIR script: - evennia migrate - evennia test evennia - evennia test

This will tell travis how to download Evennia, install it, set up a database and then run the test suite. You need to add this file to git (git add .travis.yml) and then commit your changes before Travis will be able to see it.

For properly testing your game you of course also need to write unittests. We have a page on how we set those up for

12.7. Using Travis 257 Evennia Documentation, Release 0.9

Evennia, you should be able to refer to that for making tests fitting your game.

12.8 Async Process

This is considered an advanced topic.

12.8.1 Synchronous versus Asynchronous

Most program code operates synchronously. This means that each statement in your code gets processed and finishes before the next can begin. This makes for easy-to-understand code. It is also a requirement in many cases - a subsequent piece of code often depend on something calculated or defined in a previous statement.

Consider this piece of code in a traditional Python program:

print("before call ...") long_running_function() print("after call ...")

When run, this will print "before call ...", after which the long_running_function gets to work for however long time. Only once that is done, the system prints "after call ...". Easy and logical to follow. Most of Evennia work in this way and often it’s important that commands get executed in the same strict order they were coded.

Evennia, via Twisted, is a single-process multi-user server. In simple terms this means that it swiftly switches between dealing with player input so quickly that each player feels like they do things at the same time. This is a clever illusion however: If one user, say, runs a command containing that long_running_function, all other players are effectively forced to wait until it finishes.

Now, it should be said that on a modern computer system this is rarely an issue. Very few commands run so long that other users notice it. And as mentioned, most of the time you want to enforce all commands to occur in strict sequence.

When delays do become noticeable and you don’t care in which order the command actually completes, you can run it asynchronously. This makes use of the run_async() function in src/utils/utils.py:

run_async(function, *args, **kwargs)

Where function will be called asynchronously with *args and **kwargs. Example:

258 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

from evennia import utils print("before call ...") utils.run_async(long_running_function) print("after call ...")

Now, when running this you will find that the program will not wait around for long_running_function to finish. In fact you will see "before call ..." and "after call ..." printed out right away. The long-running function will run in the background and you (and other users) can go on as normal.

12.8.2 Customizing asynchronous operation

A complication with using asynchronous calls is what to do with the result from that call. What if long_running_function returns a value that you need? It makes no real sense to put any lines of code after the call to try to deal with the result from long_running_function above - as we saw the "after call ..." got printed long before long_running_function was finished, making that line quite pointless for processing any data from the function. Instead one has to use callbacks.

utils.run_async takes reserved kwargs that won’t be passed into the long-running function: • at_return(r) (the callback) is called when the asynchronous function (long_running_function above) finishes successfully. The argument r will then be the return value of that function (or None).

def at_return(r): print(r)

• at_return_kwargs - an optional dictionary that will be fed as keyword arguments to the at_return callback. • at_err(e) (the errback) is called if the asynchronous function fails and raises an exception. This exception is passed to the errback wrapped in a Failure object e. If you do not supply an errback of your own, Evennia will automatically add one that silently writes errors to the evennia log. An example of an errback is found below:

def at_err(e): print("There was an error:", str(e))

• at_err_kwargs - an optional dictionary that will be fed as keyword arguments to the at_err errback. An example of making an asynchronous call from inside a Command definition:

from evennia import utils, Command

class CmdAsync(Command):

key="asynccommand"

def func(self):

def long_running_function(): (continues on next page)

12.8. Async Process 259 Evennia Documentation, Release 0.9

(continued from previous page) #[... lots of time-consuming code ...] return final_value

def at_return_function(r): self.caller.msg("The final value is %s"% r)

def at_err_function(e): self.caller.msg("There was an error: %s"% e)

# do the async call, setting all callbacks utils.run_async(long_running_function, at_return=at_return_function, at_err=at_

˓→err_function)

That’s it - from here on we can forget about long_running_function and go on with what else need to be done. Whenever it finishes, the at_return_function function will be called and the final value will pop up for us to see. If not we will see an error message.

12.8.3 delay

The delay function is a much simpler sibling to run_async. It is in fact just a way to delay the execution of a command until a future time. This is equivalent to something like time.sleep() except delay is asynchronous while sleep would lock the entire server for the duration of the sleep.

from evennia.utils import delay

# [...] # e.g. inside a Command, where `self.caller` is available def callback(obj): obj.msg("Returning!") delay(10, callback, self.caller)

This will delay the execution of the callback for 10 seconds. This function is explored much more in the Command Duration Tutorial.

You can also try the following snippet just see how it works:

@py from evennia.utils import delay; delay(10, lambda who: who.msg("Test!"), self)

Wait 10 seconds and ‘Test!’ should be echoed back to you.

12.8.4 The @interactive decorator

As of Evennia 0.9, the @interactive decorator is available. This makes any function or method possible to ‘pause’ and/or await player input in an interactive way.

260 Chapter 12. Coding utilities Evennia Documentation, Release 0.9

from evennia.utils import interactive

@interactive def myfunc(caller):

while True: caller.msg("Getting ready to wait ...") yield(5) caller.msg("Now 5 seconds have passed.")

response= yield("Do you want to wait another 5 secs?")

if response.lower() not in ("yes","y"): break

The @interactive decorator gives the function the ability to pause. The use of yield(seconds) will do just that - it will asynchronously pause for the number of seconds given before continuing. This is technically equivalent to using call_async with a callback that continues after 5 secs. But the code with @interactive is a little easier to follow.

Within the @interactive function, the response = yield("question") question allows you to ask the user for input. You can then process the input, just like you would if you used the Python input function. There is one caveat to this functionality though - it will only work if the function/method has an argument named exactly ‘‘caller‘‘. This is because internally Evennia will look for the caller argument and treat that as the source of input.

All of this makes the @interactive decorator very useful. But it comes with a few caveats. Notably, decorating a function/method with @interactive turns it into a Python generator. The most common issue is that you cannot use return from a generator (just an empty return works). To return a value from a function/method you have decorated with @interactive, you must instead use a special Twisted function twisted.internet.defer.returnValue. Evennia also makes this function conveniently available from evennia.utils:

from evennia.utils import interactive, returnValue

@interactive def myfunc():

# ... result= 10

# this must be used instead of `return result` returnValue(result)

12.8. Async Process 261 Evennia Documentation, Release 0.9

12.8.5 Assorted notes

Overall, be careful with choosing when to use asynchronous calls. It is mainly useful for large administration operations that have no direct influence on the game world (imports and backup operations come to mind). Since there is no telling exactly when an asynchronous call actually ends, using them for in-game commands is to potentially invite confusion and inconsistencies (and very hard-to-reproduce bugs).

The very first synchronous example above is not really correct in the case of Twisted, which is inherently an asynchronous server. Notably you might find that you will not see the first before call ... text being printed out right away. Instead all texts could end up being delayed until after the long-running process finishes. So all commands will retain their relative order as expected, but they may appear with delays or in groups.

12.8.6 Further reading

Technically, run_async is just a very thin and simplified wrapper around a Twisted Deferred object; the wrapper sets up a default errback also if none is supplied. If you know what you are doing there is nothing stopping you from bypassing the utility function, building a more sophisticated callback chain after your own liking.

262 Chapter 12. Coding utilities CHAPTER 13

Tutorials

This chapter holds tutorials and step-by-step instructions on various Evennia topics.

13.1 Python basic introduction

This is the first part of our beginner’s guide to the basics of using Python with Evennia. It’s aimed at you with limited or no programming/Python experience. But also if you are an experienced new to Evennia or Python you might still pick up a thing or two. It is by necessity brief and low on detail. There are countless Python guides and tutorials, books and videos out there for learning more in-depth - use them! Contents: • Evennia Hello world • Importing modules • Parsing Python errors • Our first function • (continued in part 2) This quickstart assumes you have gotten Evennia started. You should make sure that you are able to see the output from the server in the console from which you started it. Log into the game either with a mud client on localhost:4000 or by pointing a web browser to localhost:4001/webclient. Log in as your superuser (the user you created during install). Below, lines starting with a single > means command input.

13.1.1 Evennia Hello world

The @py (or ! which is an alias) command allows you as a superuser to run raw Python from in-game. From the game’s input line, enter the following:

263 Evennia Documentation, Release 0.9

> @py print("Hello World!")

You won’t see any return in Evennia though, instead you will just see:

>>> print("Hello world!") None

To understand what is going on: some extra info: The print(...) function is the basic, in-built way to output text in Python. The quotes "..." means you are inputing a string (i.e. text). You could also have used single-quotes '...', Python accepts both. The first return line (with >>>) is just @py echoing what you input (we won’t include that in the examples henceforth) and the last line just says the @py command finished. Where is our hello world? In Evennia, print does not print in-game. Instead it prints to the log. Open a terminal (or go back to the terminal you started Evennia in), make sure your virtualenv is active and that you are standing in your game directory (the one created with evennia --init during installation). Enter

evennia-l

to start tailing the log in the terminal (use Ctrl-C if you need to exit later). If you look towards the end you will find the print output next to the log time stamp:

2017-05-07T20:20:16+0000 [stdout#info] Hello world!

As a game dev it is important to look at the console output when working in Evennia - many errors will only appear with full details here. You may sometimes have to scroll up in the history if you miss it. To show the greeting in-game, try the following instead:

> @py me.msg("Hello world!") Hello world! None

Ignore the last None, that’s just a return from the @py command itself we don’t need to care about for now. The me is something uniquely available in the @py command (we could also use self, it’s an alias). It represents “us”, the ones calling the @py command. The me is an example of an Object instance. Objects are fundamental in Python and Evennia. The me object not only represents the character we play in the game, it also contains a lot of useful resources for doing things with that Object. One such resource is msg. msg works like print except it sends the text to the object it is attached to instead of to the console log. You access an Object’s resources by using the full-stop character .. So self.msg accesses the msg resource and then we call it like we did print, with our “Hello World!” greeting in parentheses. Important: something like print(...) we refer to as a function, while msg(...) which sits on an object is called a method. You can try printing other things. Also try to include |r at the start of your string to make the output red in-game. Use @color to learn more color tags.

13.1.2 Importing 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 structure should look:

264 Chapter 13. Tutorials Evennia Documentation, Release 0.9

mygame/ world/ test.py

For now, only add one line to test.py:

print("Hello World!")

Don’t forget to save the file. A file with the ending .py is referred to as a Python module. To use this in-game we have to import it. Try this:

> @py import world.test

If you make some error (we’ll cover how to handle errors below) you may need to run the @reload command for your changes to take effect. Think of the period . as replacing / (or \ for Windows) in your path. The .py ending of test.py is never included in this “Python-path”, but only files with that ending can be imported this way. Where is mygame in that Python-path? The answer is that Evennia has already told Python that your mygame folder is a good place to look for imports. So we don’t include mygame in the 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” to the console window. 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 in the log 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 avoid importing them more than once. So your print will only run the first time. To see it again you need to @reload first, so Python forgets about the module and has to import it again. We’ll get back to importing code in the second part of this tutorial. For now, let’s press on.

13.1.3 Parsing Python errors

Next, erase the single print statement you had in test.py and replace it with this instead:

me.msg("Hello World!")

As you recall we used this from @py earlier - it echoed “Hello World!” in-game. Save your file and @reload your server in-game - this makes sure Evennia sees the new version of your code. Try to import it from @py in the same way as earlier:

> @py import world.test

No go - this time you get an error!

File"./world/test.py", line1, in me.msg("Hello world!") NameError: name'me' is not defined

13.1. Python basic introduction 265 Evennia Documentation, Release 0.9

This is called a traceback. Python’s errors are very friendly and will most of the time tell you exactly what and where things are wrong. It’s important that you learn to parse tracebacks so you can fix your code. Let’s look at this one. A traceback is to be read from the bottom up. The last line is the error Python balked at, while the two lines above it details exactly where that error was encountered. 1. An error of type NameError is the problem . . . 2. . . . more specifically it is due to the variable me not being defined. 3. This happened on the line me.msg("Hello world!") ... 4. . . . which is on line 1 of the file ./world/test.py. In our case the traceback is short. There may be many more lines above it, tracking just how different modules called each other until it got to the faulty line. That can sometimes be useful information, but reading from the bottom is always a good start. The NameError we see here is due to a module being its own isolated thing. It knows nothing about the environment into which it is imported. It knew what print is because that is a special reserved Python keyword. But me is not such a reserved word. As far as the module is concerned me is just there out of nowhere. Hence the NameError.

13.1.4 Our first function

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 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:

def hello_world(who): who.msg("Hello World!")

Now that we are moving onto 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 etc. • 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 set up your editor to always indent 4 spaces (not a single tab-character) when you press the TAB key - it will make your life a lot easier. • def is short for “define” and defines a function (or a method, if sitting on an object). This is a reserved Python keyword; 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 with lowercase letters and underscores. Use this style for now. • who is what we call the argument to our function. Arguments are variables we pass 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 the first line indicates that the header of the function is complete. • 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 have to start 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. First, @reload your game to make it aware of the updated Python module. Now we have defined our first function, let’s use it.

266 Chapter 13. Tutorials Evennia Documentation, Release 0.9

> @reload > @py import world.test Done (use self.msg() if you want to catch output)

Nothing happened except the normal output from @py. That is because the function in our module won’t do anything just by importing it. It will only act when we call it. We will need to enter the module we just imported and do so.

> @py import world.test ; world.test.hello_world(me) Hello world! Done (use self.msg() if you want to catch output)

There is our “Hello World”! The ; is the way to put multiple Python-statements on one line. Some MUD clients use ; for their own purposes to separate client-inputs. If so you’ll get a NameError stating that world is not defined. Check so you understand why this is! Change the use of ; in your client or use the Evennia web client if this is a problem. In the second statement we access the module path we imported (world.test) and reach for the hello_world function within. We call the function with me, which becomes the who variable we use inside the hello_function. As an exercise, try to pass something else into hello_world. Try for example to pass who as the number 5 or the simple string "foo". You’ll get errors that they don’t have the attribute msg. As we’ve seen, me does make msg available which is why it works (you’ll learn more about Objects like me in the next part of this tutorial). If you are familiar with other programming languages you may be tempted to start validating who to make sure it works as expected. This is usually not recommended in Python which suggests it’s better to handle the error if it happens rather than to make a lot of code to prevent it from happening. See also duck typing. This tutorial is continued in Part 2, where we’ll start learning about objects and to explore the Evennia library.

13.2 First Steps Coding

This section gives a brief step-by-step introduction on how to set up Evennia for the first time so you can modify and overload the defaults easily. You should only need to do these steps once. It also walks through you making your first few tweaks. Before continuing, make sure you have Evennia installed and running by following the Getting Started instructions. You should have initialized a new game folder with the evennia --init foldername command. We will in the following assume this folder is called “mygame”. It might be a good idea to eye through the brief Coding Introduction too (especially the recommendations in the section about the evennia “flat” API and about using evennia shell will help you here and in the future). To follow this tutorial you also need to know the basics of operating your computer’s terminal/command line. You also need to have a text editor to edit and create source text files. There are plenty of online tutorials on how to use the terminal and plenty of good free text editors. We will assume these things are already familiar to you henceforth.

13.2.1 Your First Changes

Below are some first things to try with your new custom modules. You can test these to get a feel for the system. See also Tutorials for more step-by-step help and special cases.

13.2. First Steps Coding 267 Evennia Documentation, Release 0.9

Tweak Default Character

We will add some simple rpg attributes to our default Character. In the next section we will follow up with a new command to view those attributes. 1. Edit mygame/typeclasses/characters.py and modify the Character class. The at_object_creation method also exists on the DefaultCharacter parent and will overload it. The get_abilities method is unique to our version of Character.

class Character(DefaultCharacter): # [...] def at_object_creation(self): """ Called only at initial creation. This is a rather silly example since ability scores should vary from Character to Character and is usually set during some character generation step instead. """ #set persistent attributes self.db.strength=5 self.db.agility=4 self.db.magic=2

def get_abilities(self): """ Simple access method to return ability scores as a tuple (str,agi,mag) """ return self.db.strength, self.db.agility, self.db.magic

2. Reload the server (you will still be connected to the game after doing this). Note that if you examine yourself you will not see any new Attributes appear yet. Read the next section to understand why.

Updating Yourself

It’s important to note that the new Attributes we added above will only be stored on newly created characters. The reason for this is simple: The at_object_creation method, where we added those Attributes, is per definition only called when the object is first created, then never again. This is usually a good thing since those Attributes may change over time - calling that hook would reset them back to start values. But it also means that your existing character doesn’t have them yet. You can see this by calling the get_abilities hook on yourself at this point:

# (you have to be superuser to use @py) @py self.get_abilities() <<<( None, None, None)

This is easily remedied.

@update self

This will (only) re-run at_object_creation on yourself. You should henceforth be able to get the abilities successfully:

@py self.get_abilities() <<<(5,4,2)

This is something to keep in mind if you start building your world before your code is stable - startup-hooks will not (and should not) automatically run on existing objects - you have to update your existing objects manually. Luckily

268 Chapter 13. Tutorials Evennia Documentation, Release 0.9 this is a one-time thing and pretty simple to do. If the typeclass you want to update is in typeclasses.myclass. MyClass, you can do the following (e.g. from evennia shell): from typeclasses.myclass import MyClass # loop over all MyClass instances in the database # and call .swap_typeclass on them for obj in MyClass.objects.all(): obj.swap_typeclass(MyClass, run_start_hooks="at_object_creation")

Using swap_typeclass to the same typeclass we already have will re-run the creation hooks (this is what the @update command does under the hood). From in-game you can do the same with @py:

@py typeclasses.myclass import MyClass;[obj.swap_typeclass(MyClass) for obj in

˓→MyClass.objects.all()]

See the Object Typeclass tutorial for more help and the Typeclasses and Attributes page for detailed documentation about Typeclasses and Attributes.

Troubleshooting: Updating Yourself

One may experience errors for a number of reasons. Common beginner errors are spelling mistakes, wrong indenta- tions or code omissions leading to a SyntaxError. Let’s say you leave out a colon from the end of a class function like so: def at_object_creation(self). The client will reload without issue. However, if you look at the terminal/console (i.e. not in-game), you will see Evennia complaining (this is called a traceback):

Traceback (most recent call last): File"C:\mygame \typeclasses\characters.py", line 33 def at_object_creation(self) ^ SyntaxError: invalid syntax

Evennia will still be restarting and following the tutorial, doing @py self.get_abilities() will return the right response (None, None, None). But when attempting to @typeclass/force self you will get this response:

AttributeError:'DefaultObject' object has no attribute'get_abilities'

The full error will show in the terminal/console but this is confusing since you did add get_abilities before. Note however what the error says - you (self) should be a Character but the error talks about DefaultObject. What has happened is that due to your unhandled SyntaxError earlier, Evennia could not load the character. py module at all (it’s not valid Python). Rather than crashing, Evennia handles this by temporarily falling back to a safe default - DefaultObject - in order to keep your MUD running. Fix the original SyntaxError and reload the server. Evennia will then be able to use your modified Character class again and things should work. Note: Learning how to interpret an error traceback is a critical skill for anyone learning Python. Full trace- backs will appear in the terminal/Console you started Evennia from. The traceback text can sometimes be quite long, but you are usually just looking for the last few lines: The description of the error and the filename + line number for where the error occurred. In the example above, we see it’s a SyntaxError happening at line 33 of mygame\typeclasses\characters.py. In this case it even points out where on the line it encountered the error (the missing colon). Learn to read tracebacks and you’ll be able to resolve the vast majority of common errors easily.

13.2. First Steps Coding 269 Evennia Documentation, Release 0.9

Add a New Default Command

The @py command used above is only available to privileged users. We want any player to be able to see their stats. Let’s add a new command to list the abilities we added in the previous section. 1. Open mygame/commands/command.py. You could in principle put your command anywhere but this module has all the imports already set up along with some useful documentation. Make a new class at the bottom of this file:

class CmdAbilities(Command): """ List abilities

Usage: abilities

Displays a list of your current ability values. """ key="abilities" aliases=["abi"] lock="cmd:all()" help_category="General"

def func(self): "implements the actual functionality"

str, agi, mag= self.caller.get_abilities() string="STR: %s, AGI: %s, MAG: %s"%(str, agi, mag) self.caller.msg(string)

2. Next you edit mygame/commands/default_cmdsets.py and add a new import to it near the top:

from commands.command import CmdAbilities

3. In the CharacterCmdSet class, add the following near the bottom (it says where):

self.add(CmdAbilities())

4. Reload the server (noone will be disconnected by doing this). You (and anyone else) should now be able to use abilities (or its alias abi) as part of your normal commands in-game: abilities STR:5, AGI:4, MAG:2

See the Adding a Command tutorial for more examples and the Commands section for detailed documentation about the Command system.

Make a New Type of Object

Let’s test to make a new type of object. This example is an “wise stone” object that returns some random comment when you look at it, like this:

> look stone

A very wise stone (continues on next page)

270 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page)

This is a very wise old stone. It grumbles and says:'The world is like a rock of chocolate.'

1. Create a new module in mygame/typeclasses/. Name it wiseobject.py for this example. 2. In the module import the base Object (typeclasses.objects.Object). This is empty by default, meaning it is just a proxy for the default evennia.DefaultObject. 3. Make a new class in your module inheriting from Object. Overload hooks on it to add new functionality. Here is an example of how the file could look:

from random import choice from typeclasses.objects import Object

class WiseObject(Object): """ An object speaking when someone looks at it. We assume it looks like a stone in this example. """ def at_object_creation(self): "Called when object is first created" self.db.wise_texts=\ ["Stones have feelings too.", "To live like a stone is to not have lived at all.", "The world is like a rock of chocolate."]

def return_appearance(self, looker): """ Called by the look command. We want to return a wisdom when we get looked at. """ # first get the base string from the # parent's return_appearance. string= super().return_appearance(looker) wisewords=" \n\nIt grumbles and says:' %s'" wisewords= wisewords% choice(self.db.wise_texts) return string+ wisewords

4. Check your code for bugs. Tracebacks will appear on your command line or log. If you have a grave Syntax Error in your code, the source file itself will fail to load which can cause issues with the entire cmdset. If so, fix your bug and reload the server from the command line (noone will be disconnected by doing this). 5. Use @create/drop stone:wiseobject.WiseObject to create a talkative stone. If the @create command spits out a warning or cannot find the typeclass (it will tell you which paths it searched), re-check your code for bugs and that you gave the correct path. The @create command starts looking for Typeclasses in mygame/typeclasses/. 6. Use look stone to test. You will see the default description (“You see nothing special”) followed by a random message of stony wisdom. Use @desc stone = This is a wise old stone. to make it look nicer. See the Builder Docs for more information. Note that at_object_creation is only called once, when the stone is first created. If you make changes to this method later, already existing stones will not see those changes. As with the Character example above you can use @typeclass/force to tell the stone to re-run its initialization. The at_object_creation is a special case though. Changing most other aspects of the typeclass does not require manual updating like this - you just need to @reload to have all changes applied automatically to all existing objects.

13.2. First Steps Coding 271 Evennia Documentation, Release 0.9

13.2.2 Where to Go From Here?

There are more Tutorials, including one for building a whole little MUSH-like game - that is instructive also if you have no interest in MUSHes per se. A good idea is to also get onto the IRC chat and the mailing list to get in touch with the community and other developers.

13.3 Tutorial for basic MUSH like game

This tutorial lets you code a small but complete and functioning MUSH-like game in Evennia. A MUSH is, for our purposes, a class of roleplay-centric games focused on free form storytelling. Even if you are not interested in MUSH:es, this is still a good 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 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 a feature request from a MUSH user new to Evennia). A Character in this system should: • Have a “Power” score from 1 to 10 that measures how strong they are (stand-in for the stat system). • Have a command (e.g. +setpower 4) that sets their power (stand-in for character generation code). • Have a command (e.g. +attack) that lets them roll their power and produce a “Combat Score” between 1 and 10*Power, displaying the result and editing their object to record this number (stand-in for +actions in the command code). • Have a command that displays everyone in the room and what their most recent “Combat Score” roll was (stand-in for the combat code). • Have a command (e.g. +createNPC Jenkins) that creates an NPC with full abilities. • Have a command to control NPCs, such as +npc/cmd (name)=(command) (stand-in for the NPC control- ling code). In this tutorial we will assume you are starting from an empty database without any previous modifications.

13.3.1 Server Settings

To emulate a MUSH, the default MULTISESSION_MODE=0 is enough (one unique session per account/character). This is the default so you don’t need to change anything. You will still be able to puppet/unpuppet objects you have permission to, but there is no character selection out of the box in this mode. We will assume our game folder is called mygame henceforth. You should be fine with the default SQLite3 database.

13.3.2 Creating the Character

First thing is to choose how our Character class works. We don’t need to define a special NPC object – an NPC is after all just a Character without an Account currently controlling them. Make your changes in the mygame/typeclasses/characters.py file:

# mygame/typeclasses/characters.py from evennia import DefaultCharacter

(continues on next page)

272 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) class Character(DefaultCharacter): """ [...] """ def at_object_creation(self): "This is called when object is first created, only." self.db.power=1 self.db.combat_score=1

We defined two new Attributes power and combat_score and set them to default values. Make sure to @reload the server if you had it already running (you need to reload every time you update your python code, don’t worry, no accounts will be disconnected by the reload). Note that only new characters will see your new Attributes (since the at_object_creation hook is called when the object is first created, existing Characters won’t have it). To update yourself, run

@typeclass/force self

This resets your own typeclass (the /force switch is a safety measure to not do this accidentally), this means that at_object_creation is re-run. examine self

Under the “Persistent attributes” heading you should now find the new Attributes power and score set on yourself by at_object_creation. If you don’t, first make sure you @reloaded into the new code, next look at your server log (in the terminal/console) to see if there were any syntax errors in your code that may have stopped your new code from loading correctly.

13.3.3 Character Generation

We assume in this example that Accounts first connect into a “character generation area”. Evennia also supports full OOC menu-driven character generation, but for this example, a simple start room is enough. When in this room (or rooms) we allow character generation commands. In fact, character generation commands will only be available in such rooms. Note that this again is made so as to be easy to expand to a full-fledged game. With our simple example, we could simply set an is_in_chargen flag on the account and have the +setpower command check it. Using this method however will make it easy to add more functionality later. What we need are the following: • One character generation Command to set the “Power” on the Character. • A chargen CmdSet 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.

The +setpower command

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 to set the Character s “power” stat. It will be called on the following MUSH-like form:

13.3. Tutorial for basic MUSH like game 273 Evennia Documentation, Release 0.9

+setpower4

Open command.py file. It contains documented empty templates for the base command and the “MuxCommand” type used by default in Evennia. We will use the plain Command type here, the MuxCommand class offers some extra features like stripping whitespace that may be useful - if so, just import from that instead. Add the following to the end of the command.py file:

# end of command.py from evennia import Command # just for clarity; already imported above class CmdSetPower(Command): """ set the power of a character

Usage: +setpower <1-10>

This sets the power of the current character. This can only be used during character generation. """

key="+setpower" help_category=""

def func(self): "This performs the actual command" errmsg="You must supply a number between 1 and 10." if not self.args: self.caller.msg(errmsg) return try: power= int(self.args) except ValueError: self.caller.msg(errmsg) return if not (1<= power<= 10): self.caller.msg(errmsg) return # at this point the argument is tested as valid. Let's set it. self.caller.db.power= power self.caller.msg("Your Power was set to %i."% power)

This is a pretty straightforward command. We do some error checking, then set the power on ourself. 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 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 import the default CmdSet class for the next step: from evennia import CmdSet from commands import command

Next scroll down and define a new command set (based on the base CmdSet class we just imported at the end of this file, to hold only our chargen-specific command(s):

274 Chapter 13. Tutorials Evennia Documentation, Release 0.9

# end of default_cmdsets.py

class ChargenCmdset(CmdSet): """ This cmdset it used in character generation areas. """ key="Chargen" def at_cmdset_creation(self): "This is called at initialization" self.add(command.CmdSetPower())

In the future you can add any number of commands to this cmdset, to expand your character generation system as you desire. Now we need to actually put that cmdset on something so it’s made available to users. We could put it directly on the Character, but that would make it available all the time. It’s cleaner to put it on a room, so it’s only available when players are in that room.

Chargen areas

We will create a simple Room typeclass to act as a template for all our Chargen areas. Edit mygame/typeclasses/ rooms.py next:

# end of rooms.py

from commands.default_cmdsets import ChargenCmdset

class ChargenRoom(Room): """ This room class is used by character-generation rooms. It makes the ChargenCmdset available. """ def at_object_creation(self): "this is called only at first creation" self.cmdset.add(ChargenCmdset, permanent=True)

Note how new rooms created with this typeclass will always start with ChargenCmdset on themselves. Don’t forget the permanent=True keyword or you will lose the cmdset after a server reload. For more information about Command Sets and Commands, see the respective links.

Testing chargen

First, make sure you have @reloaded the server (or use evennia reload from the terminal) to have your new python code added to the game. Check your terminal and fix any errors you see - the error traceback lists exactly where the error is found - look line numbers in files you have changed. We can’t test things unless we have some chargen areas to test. Log into the game (you should at this point be using the new, custom Character class). Let’s dig a chargen area to test.

@dig chargen:rooms.ChargenRoom= chargen,finish

If you read the help for @dig you will find that this will create a new room named chargen. The part after the : is the python-path to the Typeclass you want to use. Since Evennia will automatically try the typeclasses folder of our game directory, we just specify rooms.ChargenRoom, meaning it will look inside the module rooms.py for a class named ChargenRoom (which is what we created above). The names given after = are the names of exits to and from the room from your current location. You could also append aliases to each one name, such as chargen;character generation.

13.3. Tutorial for basic MUSH like game 275 Evennia Documentation, Release 0.9

So in summary, this will create a new room of type ChargenRoom and open an exit chargen to it and an exit back here named finish. If you see errors at this stage, you must fix them in your code. @reload between fixes. Don’t continue until the creation seems to have worked okay.

chargen

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 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 line for tracebacks and errors.

13.3.4 Combat System

We will add our combat command to the default command set, meaning it will be available to everyone at all times. The combat system consists of a +attack command to get how successful our attack is. We also change the default look command to display the current combat score.

Attacking with the +attack command

Attacking in this simple system means rolling a random “combat score” influenced by the power stat set during Character generation:

> +attack You +attack with a combat score of 12!

Go back to mygame/commands/command.py and add the command to the end like this: import random class CmdAttack(Command): """ issues an attack

Usage: +attack

This will calculate a new combat score based on your Power. Your combat score is visible to everyone in the same location. """ key="+attack" help_category="mush"

def func(self): "Calculate the random score between 1-10*Power" caller= self.caller power= caller.db.power if not power: # this can happen if caller is not of # our custom Character typeclass power=1 (continues on next page)

276 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) combat_score= random.randint(1, 10 * power) caller.db.combat_score= combat_score

# announce message=" %s +attack%s with a combat score of %s!" caller.msg(message%("You","", combat_score)) caller.location.msg_contents(message% (caller.key,"s", combat_score), exclude=caller)

What we do here is simply to generate a “combat score” using Python’s inbuilt random.randint() function. We then store that and echo the result to everyone involved. To make the +attack command available to you in game, go back to mygame/commands/default_cmdsets. py and scroll down to the CharacterCmdSet class. At the correct place add this line:

self.add(command.CmdAttack())

@reload Evennia and the +attack command should be available to you. Run it and use e.g. @ex to make sure the combat_score attribute is saved correctly.

Have “look” show combat scores

Players should be able to view all current combat scores in the room. We could do this by simply adding a second command named something like +combatscores, but we will instead let the default look command do the heavy lifting for us and display our scores as part of its normal output, like this:

> look Tom Tom (combat score:3) This is a great warrior.

We don’t actually have to modify the look command itself however. To understand why, take a look at how the default look is actually defined. It sits in evennia/commands/default/general.py (or browse it on- line here). You will find that the actual return text is done by the look command calling a hook method named return_appearance on the object looked at. All the look does is to echo whatever this hook returns. So what we need to do is to edit our custom Character typeclass and overload its return_appearance to return what we want (this is where the advantage of having a custom typeclass comes into play for real). Go back to your custom Character typeclass in mygame/typeclasses/characters.py. The default imple- mentation of return appearance is found in evennia.DefaultCharacter (or online here). If you want to make bigger changes you could copy & paste the whole default thing into our overloading method. In our case the change is small though:

class Character(DefaultCharacter): """ [...] """ def at_object_creation(self): "This is called when object is first created, only." self.db.power=1 self.db.combat_score=1

def return_appearance(self, looker): """ The return from this method is what (continues on next page)

13.3. Tutorial for basic MUSH like game 277 Evennia Documentation, Release 0.9

(continued from previous page) looker sees when looking at this object. """ text= super().return_appearance(looker) cscore=" (combat score: %s)"% self.db.combat_score if "\n" in text: # text is multi-line, add score after first line first_line, rest= text.split(" \n",1) text= first_line+ cscore+" \n"+ rest else: # text is only one line; add score to end text+= cscore return text

What we do is to simply let the default return_appearance do its thing (super will call the parent’s version of the same method). We then split out the first line of this text, append our combat_score and put it back together again. @reload the server and you should be able to look at other Characters and see their current combat scores. Note: A potentially more useful way to do this would be to overload the entire return_appearance of the Rooms of your mush and change how they list their contents; in that way one could see all combat scores of all present Characters at the same time as looking at the room. We leave this as an exercise.

13.3.5 NPC system

Here we will re-use the Character class by introducing a command that can create NPC objects. We should also be able to set its Power and order it around. There are a few ways to define the NPC class. We could in theory create a custom typeclass for it and put a custom NPC-specific cmdset on all NPCs. This cmdset could hold all manipulation commands. Since we expect NPC manip- ulation 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.

Creating an NPC with +createNPC

We need a command for creating the NPC, this is a very straightforward command:

>+createnpc Anna You created the NPC'Anna'.

At the end of command.py, create our new command: from evennia import create_object class CmdCreateNPC(Command): """ create a new npc

Usage: +createNPC

Creates a new, named NPC. The NPC will start with a Power of 1. """ key="+createnpc" (continues on next page)

278 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) aliases=["+createNPC"] locks="call:not perm(nonpcs)" help_category="mush"

def func(self): "creates the object and names it" caller= self.caller if not self.args: caller.msg("Usage: +createNPC ") return if not caller.location: # may not create npc when OOC caller.msg("You must have a location to create an npc.") return # make name always start with capital letter name= self.args.strip().capitalize() # create npc in caller's location npc= create_object("characters.Character", key=name, location=caller.location, locks="edit:id(%i) and perm(Builders);call:false()"% caller.id) # announce message=" %s created the NPC' %s'." caller.msg(message%("You", name)) caller.location.msg_contents(message% (caller.key, name), exclude=caller)

Here we define a +createnpc (+createNPC works too) that is callable by everyone not having the nonpcs “permission” (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 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 as the Charac- ter creating them, this might be a security risk though. Add this command to your default cmdset the same way you did the +attack command earlier. @reload and it will be available to test.

Editing the NPC with +editNPC

Since we re-used our custom character typeclass, our new NPC already has a Power value - it 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 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

The @set command is too generally powerful though, and thus only available to staff. We will add a custom com- mand that only changes the things we want players to be allowed to change. We could in principle re-work our old +setpower command, but let’s try something more useful. Let’s make a +editNPC command.

13.3. Tutorial for basic MUSH like game 279 Evennia Documentation, Release 0.9

>+editNPC Anna/power= 10 Set Anna's property'power' to 10.

This is a slightly more complex command. It goes at the end of your command.py file as before. class CmdEditNPC(Command): """ edit an existing NPC

Usage: +editnpc [/ [= value]]

Examples: +editnpc mynpc/power = 5 +editnpc mynpc/power - displays power value +editnpc mynpc - shows all editable attributes and values

This command edits an existing NPC. You must have permission to edit the NPC to use this. """ key="+editnpc" aliases=["+editNPC"] locks="cmd:not perm(nonpcs)" help_category="mush"

def parse(self): "We need to do some parsing here" args= self.args propname, propval= None, None if "=" in args: args, propval= [part.strip() for part in args.rsplit("=",1)] if "/" in args: args, propname= [part.strip() for part in args.rsplit("/",1)] # store, so we can access it below in func() self.name= args self.propname= propname # a propval without a propname is meaningless self.propval= propval if propname else None

def func(self): "do the editing"

allowed_propnames=("power","attribute1","attribute2")

caller= self.caller if not self.args or not self.name: caller.msg("Usage: +editnpc name[/propname][=propval]") return npc= caller.search(self.name) if not npc: return if not npc.access(caller,"edit"): caller.msg("You cannot change this NPC.") return if not self.propname: # this means we just list the values (continues on next page)

280 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) output="Properties of %s:"% npc.key for propname in allowed_propnames: propvalue= npc.attributes.get(propname, default="N/A") output+=" \n %s = %s"% (propname, propvalue) caller.msg(output) elif self.propname not in allowed_propnames: caller.msg("You may only change %s."% ",".join(allowed_propnames)) elif self.propval: # assigning a new propvalue # in this example, the properties are all integers... intpropval= int(self.propval) npc.attributes.add(self.propname, intpropval) caller.msg("Set %s's property' %s' to %s"% (npc.key, self.propname, self.propval)) else: # propname set, but not propval - show current value caller.msg("%s has property %s = %s"% (npc.key, self.propname, npc.attributes.get(self.propname, default="N/A")))

This command example shows off the use of more advanced parsing but otherwise it’s mostly error checking. It searches for the given npc in the same room, and checks so the caller actually has permission to “edit” it before continuing. An account without the proper permission won’t even be able to view the properties on the given NPC. It’s up to each game if this is the way it should be. Add this to the default command set like before and you should be able to try it out. Note: If you wanted a player to use this command to change an on-object property like the NPC’s name (the ‘‘key‘‘ property), you’d need to modify the command since “key” is not an Attribute (it is not retrievable via ‘‘npc.attributes.get‘‘ but directly via ‘‘npc.key‘‘). We leave this as an optional exercise.

Making the NPC do stuff - the +npc command

Finally, we will make a command to order our NPC around. For now, we will limit this command to only be usable by those having the “edit” permission on the NPC. This can be changed if it’s possible for anyone to use the NPC. The NPC, since it inherited our Character typeclass has access to most commands a player does. What it doesn’t have access to are Session and Player-based cmdsets (which means, among other things that they cannot chat on channels, but they could do that if you just added those commands). This makes the +npc command simple:

+npc Anna = say Hello! Anna says, 'Hello!'

Again, add to the end of your command.py module: class CmdNPC(Command): """ controls an NPC

Usage: +npc =

This causes the npc to perform a command as itself. It will do so with its own permissions and accesses. """ (continues on next page)

13.3. Tutorial for basic MUSH like game 281 Evennia Documentation, Release 0.9

(continued from previous page) key="+npc" locks="call:not perm(nonpcs)" help_category="mush"

def parse(self): "Simple split of the = sign" name, cmdname= None, None if "=" in self.args: name, cmdname= [part.strip() for part in self.args.rsplit("=",1)] self.name, self.cmdname= name, cmdname

def func(self): "Run the command" caller= self.caller if not self.cmdname: caller.msg("Usage: +npc = ") return npc= caller.search(self.name) if not npc: return if not npc.access(caller,"edit"): caller.msg("You may not order this NPC to do anything.") return # send the command order npc.execute_cmd(self.cmdname) caller.msg("You told %s to do' %s'."% (npc.key, self.cmdname))

Note that if you give an erroneous command, you will not see any error message, since that error will be returned to the npc object, not to you. If you want players to see this, you can give the caller’s session ID to the execute_cmd call, like this:

npc.execute_cmd(self.cmdname, sessid=self.caller.sessid)

Another thing to remember is however that this is a very simplistic way to control NPCs. Evennia supports full pup- peting very easily. An Account (assuming the “puppet” permission was set correctly) could simply do @ic mynpc and be able to play the game “as” that NPC. This is in fact just what happens when an Account takes control of their normal Character as well.

13.3.6 Concluding remarks

This ends the tutorial. It looks like a lot of text but the amount of code you have to write is actually relatively short. At this point you should have a basic skeleton of a game and a feel for what is involved in coding your game. From here on you could build a few more ChargenRooms and link that to a bigger grid. The +setpower command can either be built upon or accompanied by many more to get a more elaborate character generation. The simple “Power” game mechanic should be easily expandable to something more full-fledged and 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. For more specific ideas, see the other tutorials and hints as well as the Developer Central.

282 Chapter 13. Tutorials Evennia Documentation, Release 0.9

13.4 Adding Command Tutorial

This is a quick first-time tutorial expanding on the Commands documentation.

Let’s assume you have just downloaded Evennia, installed it and created your game folder (let’s call it just mygame here). Now you want to try to add a new command. This is the fastest way to do it.

13.4.1 Step 1: Creating a custom command

1. Open mygame/commands/command.py in a text editor. This is just one place commands could be placed but you get it setup from the onset as an easy place to start. It also already contains some example code. 2. Create a new class in command.py inheriting from default_cmds.MuxCommand. Let’s call it CmdEcho in this example. 3. Set the class variable key to a good command name, like echo. 4. Give your class a useful docstring. A docstring is the string at the very top of a class or function/method. The docstring at the top of the command class is read by Evennia to become the help entry for the Command (see Command Auto-help). 5. Define a class method func(self) that echoes your input back to you. Below is an example how this all could look for the echo command:

# file mygame/commands/command.py #[...] from evennia import default_cmds class CmdEcho(default_cmds.MuxCommand): """ Simple command example

Usage: echo [text]

This command simply echoes text back to the caller. """

key="echo"

def func(self): "This actually does things" if not self.args: self.caller.msg("You didn't enter anything!") else: self.caller.msg("You gave the string:' %s'"% self.args)

13.4.2 Step 2: Adding the Command to a default Cmdset

The command is not available to use until it is part of a Command Set. In this example we will go the easiest route and add it to the default Character commandset that already exists.

13.4. Adding Command Tutorial 283 Evennia Documentation, Release 0.9

1. Edit mygame/commands/default_cmdsets.py 2. Import your new command with from commands.command import CmdEcho. 3. Add a line self.add(CmdEcho()) to CharacterCmdSet, in the at_cmdset_creation method (the template tells you where). This is approximately how it should look at this point:

# file mygame/commands/default_cmdsets.py #[...] from commands.command import CmdEcho #[...] class CharacterCmdSet(default_cmds.CharacterCmdSet):

key="DefaultCharacter"

def at_cmdset_creation(self):

# this first adds all default commands super(DefaultSet, self).at_cmdset_creation()

# all commands added after this point will extend or # overwrite the default commands. self.add(CmdEcho())

Next, run the @reload command. You should now be able to use your new echo command from inside the game. Use help echo to see the documentation for the command.

If you have trouble, make sure to check the log for error messages (probably due to syntax errors in your command definition).

Note: Typing echotest will also work. It will be handled as the command echo directly followed by its argument test (which will end up in self.args). To change this behavior, you can add thearg_regexproperty alongsidekey,help_category‘ etc. See the arg_regex documentation for more info.

If you want to overload existing default commands (such as look or get), just add your new command with the same key as the old one - it will then replace it. Just remember that you must use @reload to see any changes.

See Commands for many more details and possibilities when defining Commands and using Cmdsets in various ways.

13.4.3 Adding the command to specific object types

Adding your Command to the CharacterCmdSet is just one easy exapmple. The cmdset system is very generic. You can create your own cmdsets (let’s say in a module mycmdsets.py) and add them to objects as you please (how to control their merging is described in detail in the ‘Command Set documentation‘_).

284 Chapter 13. Tutorials Evennia Documentation, Release 0.9

# file mygame/commands/mycmdsets.py #[...] from commands.command import CmdEcho from evennia import CmdSet #[...] class MyCmdSet(CmdSet):

key="MyCmdSet"

def at_cmdset_creation(self): self.add(CmdEcho())

Now you just need to add this to an object. To test things (as superuser) you can do

@py self.cmdset.add("mycmdsets.MyCmdSet")

This will add this cmdset (along with its echo command) to yourself so you can test it. Note that you cannot add a single Command to an object on its own, it must be part of a CommandSet in order to do so.

The Command you added is not there permanently at this point. If you do a @reload the merger will be gone. You could add the permanent=True keyword to the cmdset.add call. This will however only make the new merged cmdset permanent on that single object. Often you want all objects of this particular class to have this cmdset.

To make sure all new created objects get your new merged set, put the cmdset.add call in your custom ‘Typeclasses‘_’ at_object_creation method:

# e.g. in mygame/typeclasses/objects.py from evennia import DefaultObject class MyObject(DefaultObject):

def at_object_creation(self): "called when the object is first created" self.cmdset.add("mycmdset.MyCmdSet", permanent=True)

All new objects of this typeclass will now start with this cmdset and it will survive a @reload.

Note: An important caveat with this is that at_object_creation is only called once, when the object is first created. This means that if you already have existing objects in your databases using that typeclass, they will not have been initiated the same way. There are many ways to update them; since it’s a one-time update you can usually just simply loop through them. As superuser, try the following:

@py from typeclasses.objects import MyObject; [.cmdset.add("mycmdset.MyCmdSet") for

˓→o in MyObject.objects.all()] (continues on next page)

13.4. Adding Command Tutorial 285 Evennia Documentation, Release 0.9

(continued from previous page)

This goes through all objects in your database having the right typeclass, adding the new cmdset to each. The good news is that you only have to do this if you want to post-add cmdsets. If you just want to add a new command, you can simply add that command to the cmdset’s at_cmdset_creation and @reload to make the Command immediately available.

13.4.4 Change where Evennia looks for command sets

Evennia uses settings variables to know where to look for its default command sets. These are normally not changed unless you want to re-organize your game folder in some way. For example, the default character cmdset defaults to being defined as

CMDSET_CHARACTER="commands.default_cmdset.CharacterCmdSet"

See evennia/settings_default.py for the other settings. documentation: Command-Sets .. _Typeclasses: Typeclasses.html

13.5 Adding Object Typeclass Tutorial

Evennia comes with a few very basic classes of in-game entities:

DefaultObject | DefaultCharacter DefaultRoom DefaultExit DefaultChannel

When you create a new Evennia game (with for example evennia --init mygame) Evennia will automatically create empty child classes Object, Character, Room and Exit respectively. They are found mygame/typeclasses/objects.py, mygame/typeclasses/rooms.py etc.

Technically these are all Typeclassed, which can be ignored for now. In mygame/typeclasses are also base typeclasses for out-of-character things, notably Channels, Accounts and Scripts. We don’t cover those in this tutorial.

For your own game you will most likely want to expand on these very simple beginnings. It’s normal to want your Characters to have various attributes, for example. Maybe Rooms should hold extra information or even all Objects in your game should have properties not included in basic Evennia.

286 Chapter 13. Tutorials Evennia Documentation, Release 0.9

13.5.1 Change Default Rooms, Exits, Character Typeclass

This is the simplest case.

The default build commands of a new Evennia game is set up to use the Room, Exit and Character classes found in the same-named modules under mygame/typeclasses/. By default these are empty and just implements the default parents from the Evennia library (DefaultRoometc). Just add the changes you want to these classes and run @reload to add your new functionality.

13.5.2 Create a new type of object

Say you want to create a new “Heavy” object-type that characters should not have the ability to pick up.

1. Edit mygame/typeclasses/objects.py (you could also create a new module there, named something like heavy.py, that’s up to how you want to organize things). 2. Create a new class inheriting at any distance from DefaultObject. It could look something like this:

# end of file mygame/typeclasses/objects.py from evennia import DefaultObject

class Heavy(DefaultObject): "Heavy object" def at_object_creation(self): "Called whenever a new object is created" # lock the object down by default self.locks.add("get:false()") # the default "get" command looks for this Attribute in order # to return a customized error message (we just happen to know # this, you'd have to look at the code of the 'get' command to # find out). self.db.get_err_msg="This is too heavy to pick up."

3. Once you are done, log into the game with a build-capable account and do @create/drop rock:objects.Heavy to drop a new heavy “rock” object in your location. Next try to pick it up (@quell yourself first if you are a superuser). If you get errors, look at your log files where you will find the traceback. The most common error is that you have some sort of syntax error in your class.

Note that the Locks and Attribute which are set in the typeclass could just as well have been set using commands in-game, so this is a very simple example.

13.5.3 Storing data on initialization

The at_object_creation is only called once, when the object is first created. This makes it ideal for database-bound things like Attributes. But sometimes you want to create temporary properties (things that are not to be stored in the database but still always exist every time the

13.5. Adding Object Typeclass Tutorial 287 Evennia Documentation, Release 0.9

object is created). Such properties can be initialized in the at_init method on the object. at_init is called every time the object is loaded into memory.

Note: It’s usually pointless and wasteful to assign database data in at_init, since this will hit the database with the same value over and over. Put those in at_object_creation instead.

You are wise to use ndb (non-database Attributes) to store these non-persistent properties, since ndb-properties are protected against being cached out in various ways and also allows you to list them using various in-game tools:

def at_init(self): self.ndb.counter = 0 self.ndb.mylist = []

| Note: As mentioned in the `Typeclasses`_ documentation, ``at_init`` replaces the use of | the standard ``__init__`` method of typeclasses due to how the latter may be called in situations | other than you’d expect. So use ``at_init`` where you would normally use ``__init__``.

13.5.4 Updating existing objects

If you already have some Heavy objects created and you add a new Attribute in at_object_creation, you will find that those existing objects will not have this Attribute. This is not so strange, since at_object_creation is only called once, it will not be called again just because you update it. You need to update existing objects manually.

If the number of objects is limited, you can use @typeclass/force/reload objectname to force a re-load of the at_object_creation method (only) on the object. This case is common enough that there is an alias @update objectname you can use to get the same effect. If there are multiple objects you can use @py to loop over the objects you need:

@py from typeclasses.objects import Heavy; [obj.at_object_creation() for obj in Heavy.

˓→objects.all()]

13.6 Command Prompt

A prompt is quite common in MUDs. The prompt display useful details about your character that you are likely to want to keep tabs on at all times, such as health, magical power etc. It might also show things like in-game time, weather and so on. Many modern MUD clients (including Evennia’s own webclient) allows for identifying the prompt and have it appear in a correct location (usually just above the input line). Usually it will remain like that until it is explicitly updated.

288 Chapter 13. Tutorials Evennia Documentation, Release 0.9

13.6.1 Sending a prompt

A prompt is sent using the prompt keyword to the msg() method on objects. The prompt will be sent without any line breaks. self.msg(prompt="HP: 5, MP: 2, SP: 8")

You can combine the sending of normal text with the sending (updating of the prompt): self.msg("This is a text", prompt="This is a prompt")

You can update the prompt on demand, this is normally done using OOB-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. Here is a simple example of the prompt sent/updated from a command class: from evennia import Command class CmdDiagnose(Command): """ see how hurt your are

Usage: diagnose [target]

This will give an estimate of the target's health. Also the target's prompt will be updated. """ key="diagnose"

def func(self): if not self.args: target= self.caller else: target= self.search(self.args) if not target: return # try to get health, mana and stamina hp= target.db.hp mp= target.db.mp sp= target.db.sp

if None in (hp, mp, sp): # Attributes not defined self.caller.msg("Not a valid target!") return

text="You diagnose %s as having"\ "%i health, %i mana and %i stamina."\ % (hp, mp, sp) prompt=" %i HP, %i MP, %i SP"% (hp, mp, sp) self.caller.msg(text, prompt=prompt)

13.6. Command Prompt 289 Evennia Documentation, Release 0.9

13.6.2 A prompt sent with every command

The prompt sent as described above uses a standard telnet instruction (the Evennia web client gets a special flag). Most MUD telnet clients will understand and allow users to catch this and keep the prompt in place until it updates. So in principle you’d not need to update the prompt every command. However, with a varying user base it can be unclear which clients are used and which skill level the users have. So sending a prompt with every command is a safe catch-all. You don’t need to manually go in and edit every command you have though. Instead you edit the base command class for your custom commands (like MuxCommand in your mygame/commands/command.py folder) and overload the at_post_cmd() hook. This hook is always called after the main func() method of the Command.

from evennia import default_cmds

class MuxCommand(default_cmds.MuxCommand): # ... def at_post_cmd(self): "called after self.func()." caller= self.caller prompt=" %i HP, %i MP, %i SP"% (caller.db.hp, caller.db.mp, caller.db.sp) caller.msg(prompt=prompt)

Modifying default commands

If you want to add something small like this to Evennia’s default commands without modifying them directly the easiest way is to just wrap those with a multiple inheritance to your own base class:

# in (for example) mygame/commands/mycommands.py

from evennia import default_cmds # our custom MuxCommand with at_post_cmd hook from commands.command import MuxCommand

# overloading the look command class CmdLook(default_cmds.CmdLook, MuxCommand): pass

The result of this is that the hooks from your custom MuxCommand will be mixed into the default CmdLook through multiple inheritance. Next you just add this to your default command set:

# in mygame/commands/default_cmdsets.py

from evennia import default_cmds from commands import mycommands

class CharacterCmdSet(default_cmds.CharacterCmdSet): # ... def at_cmdset_creation(self): # ... self.add(mycommands.CmdLook())

This will automatically replace the default look command in your game with your own version.

290 Chapter 13. Tutorials Evennia Documentation, Release 0.9

13.7 NPC shop Tutorial

This tutorial will describe how to make an NPC-run shop. We will make use of the EvMenu 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 room” holding the wares the shop should be able to sell. We aim for the following features: • The front room should have an Attribute storeroom that points to the store room. • Inside the front room, the customer should have a command buy or browse. This will open a menu listing all items available to buy from the store room. • A customer should be able to look at individual items before buying. • We use “gold” as an example currency. To determine cost, the system will look for an Attribute gold_value on the items in the store room. If not found, a fixed base value of 1 will be assumed. The wealth of the customer should be set as an Attribute gold on the Character. If not set, they have no gold and can’t buy anything. • When the customer makes a purchase, the system will check the gold_value of the goods and compare it to the gold Attribute of the customer. If enough gold is available, this will be 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.

13.7.1 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 if you are not familiar with it.

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:

*** Welcome to ye Old Sword shop! *** Things for sale (choose 1-3 to inspect, quit to exit): ______1. A rusty sword (5 gold) 2. A sword with a leather handle (10 gold) 3. Excalibur (100 gold)

There are only three items to buy in this example but the menu should expand to however many items are needed. When you make a selection you will get a new screen showing the options for that particular item:

You inspect A rusty sword:

This is an old weapon maybe once used by soldiers in some long forgotten army. It is rusty and in bad condition. ______1. Buy A rusty sword (5 gold) 2. Look for something else.

Finally, when you buy something, a brief message should pop up:

13.7. NPC shop Tutorial 291 Evennia Documentation, Release 0.9

You pay 5 gold and purchase A rusty sword!

or

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

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 automatically make the quit command available to us so we won’t add that manually. For compactness we will put everything needed for our shop in one module, mygame/typeclasses/npcshop.py.

# mygame/typeclasses/npcshop.py

from evennia.utils import evmenu

def menunode_shopfront(caller): "This is the top-menu screen."

shopname= caller.location.key wares= caller.location.db.storeroom.contents

# Wares includes all items inside the storeroom, including the # door! Let's remove that from our for sale list. wares= [ware for ware in wares if ware.key.lower() !="door"]

text=" *** Welcome to %s! ***\n"% shopname if wares: text+=" Things for sale (choose 1- %i to inspect);"\ " quit to exit:"% len(wares) else: text+=" There is nothing for sale; quit to exit."

options=[] for ware in wares: # add an option for every ware in store options.append({"desc":" %s (%s gold)"% (ware.key, ware.db.gold_value or 1), "goto":"menunode_inspect_and_buy"}) return text, options

In this code we assume the caller to be inside the shop when accessing the menu. This means we can access the shop room via caller.location and get its key to display as the shop’s name. We also assume the shop has an Attribute storeroom we can use to get to our stock. We loop over our goods to build up the menu’s options. Note that all options point to the same menu node called menunode_inspect_and_buy! We can’t know which goods will be available to sale so we rely on this node to modify itself depending on the circumstances. Let’s create it now.

# further down in mygame/typeclasses/npcshop.py

def menunode_inspect_and_buy(caller, raw_string): "Sets up the buy menu screen." (continues on next page)

292 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page)

wares= caller.location.db.storeroom.contents # Don't forget, we will need to remove that pesky door again! wares= [ware for ware in wares if ware.key.lower() !="door"] iware= int(raw_string)-1 ware= wares[iware] value= ware.db.gold_value or 1 wealth= caller.db.gold or 0 text="You inspect %s:\n\n%s"% (ware.key, ware.db.desc)

def buy_ware_result(caller): "This will be executed first when choosing to buy." if wealth>= value: rtext="You pay %i gold and purchase %s!"%\ (value, ware.key) caller.db.gold-= value ware.move_to(caller, quiet=True) else: rtext="You cannot afford %i gold for %s!"%\ (value, ware.key) caller.msg(rtext)

options=({"desc":"Buy %s for %s gold"%\ (ware.key, ware.db.gold_value or 1), "goto":"menunode_shopfront", "exec": buy_ware_result}, {"desc":"Look for something else", "goto":"menunode_shopfront"})

return text, options

In this menu node we make use of the raw_string argument to the node. This is the text the menu user entered on the previous node to get here. Since we only allow numbered options in our menu, raw_input must be an number for the player to get to this point. So we convert it to an integer index (menu lists start from 1, whereas Python indices always starts at 0, so we need to subtract 1). We then use the index to get the corresponding item from storage. We just show the customer the desc of the item. In a more elaborate setup you might want to show things like weapon damage and special stats here as well. When the user choose the “buy” option, EvMenu will execute the exec instruction before we go back to the top node (the goto instruction). For this we make a little inline function buy_ware_result. EvMenu will call the function given to exec like any menu node but it does not need to return anything. In buy_ware_result we determine if the customer can afford the cost and 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

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 for customers to explicitly wanting to shop around.

# mygame/typeclasses/npcshop.py

from evennia import Command

(continues on next page)

13.7. NPC shop Tutorial 293 Evennia Documentation, Release 0.9

(continued from previous page) class CmdBuy(Command): """ Start to do some shopping

Usage: buy shop browse

This will allow you to browse the wares of the current shop and buy items you want. """ key="buy" aliases=("shop","browse")

def func(self): "Starts the shop EvMenu instance" evmenu.EvMenu(self.caller, "typeclasses.npcshop", startnode="menunode_shopfront")

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 so we can add it correctly to the game: from evennia import CmdSet class ShopCmdSet(CmdSet): def at_cmdset_creation(self): self.add(CmdBuy())

13.7.2 Building the shop

There are really only two things that separate our shop from any other Room: • The shop has the storeroom Attribute set on it, pointing to a second (completely normal) room. • It has the ShopCmdSet stored on itself. This makes the buy command available to users entering 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 for the shop room and make a small command that builders can use to build both the shop and the storeroom at once.

# bottom of mygame/typeclasses/npcshop.py from evennia import DefaultRoom, DefaultExit, DefaultObject from evennia.utils.create import create_object

# class for our front shop room class NPCShop(DefaultRoom): def at_object_creation(self): # we could also use add(ShopCmdSet, permanent=True) self.cmdset.add_default(ShopCmdSet) self.db.storeroom= None

# command to build a complete shop (the Command base class (continues on next page)

294 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) # should already have been imported earlier in this file) class CmdBuildShop(Command): """ Build a new shop

Usage: @buildshop shopname

This will create a new NPCshop room as well as a linked store room (named simply -storage) for the wares on sale. The store room will be accessed through a locked door in the shop. """ key="@buildshop" locks="cmd:perm(Builders)" help_category="Builders"

def func(self): "Create the shop rooms" if not self.args: self.msg("Usage: @buildshop ") return # create the shop and storeroom shopname= self.args.strip() shop= create_object(NPCShop, key=shopname, location=None) storeroom= create_object(DefaultRoom, key="%s-storage"% shopname, location=None) shop.db.storeroom= storeroom # create a door between the two shop_exit= create_object(DefaultExit, key="back door", aliases=["storage","store room"], location=shop, destination=storeroom) storeroom_exit= create_object(DefaultExit, key="door", location=storeroom, destination=shop) # make a key for accessing the store room storeroom_key_name=" %s-storekey"% shopname storeroom_key= create_object(DefaultObject, key=storeroom_key_name, location=shop) # only allow chars with this key to enter the store room shop_exit.locks.add("traverse:holds(%s)"% storeroom_key_name)

# inform the builder about progress self.caller.msg("The shop %s was created!"% shop)

Our typeclass is simple and so is our buildshop command. The command (which is for Builders only) just takes the name of the shop and builds the front room and a store room to go with it (always named "-storage". It connects the rooms with a two-way exit. You need to add CmdBuildShop to the default cmdset before you can

13.7. NPC shop Tutorial 295 Evennia Documentation, Release 0.9 use it. Once having created the shop you can now @teleport to it or @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 on the storage door. It’s a simple lock that requires the one entering to carry an object named -storekey. We even create such a key object and drop it in the shop for the new shop keeper to pick up. If players are given the right to name their own objects, this simple lock is not very secure and you need to come up with a more robust lock-key solution. We don’t add any descriptions to all these objects so looking “at” them will not be too thrilling. You could add better default descriptions as part of the @buildshop command or leave descriptions this up to the Builder.

13.7.3 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 room. Our shop does have some shortcomings: • For Characters to be able to buy stuff they need to also have the gold Attribute set on themselves. • We manually remove the “door” exit from our items for sale. But what if there are other unsellable items in the store room? What if the shop owner walks in there for example - anyone in the store could then buy them for 1 gold. • What if someone else were to buy the item we’re looking at just before we decide to buy it? It would then be gone and the counter be wrong - the shop would pass us the next item in the list. Fixing these issues are left as an exercise. If you want to keep the shop fully NPC-run you could add a Script 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.

13.8 Static In Game Map

13.8.1 Introduction

This tutorial describes the creation of an in-game map display based on a pre-drawn map. It also details how to use the ‘Batch code processor‘_ for advanced building. There is also the Dynamic in-game map tutorial that works in the opposite direction, by generating a map from an existing grid of rooms. Evennia does not require its rooms to be positioned in a “logical” way. Your exits could be named anything. You could make an exit “west” that leads to a room described to be in the far north. You could have rooms inside one another, exits leading back to the same room or describing spatial geometries impossible in the real world. That said, most games do organize their rooms in a logical fashion, if nothing else to retain the sanity of their players. And when they do, the game becomes possible to map. This tutorial will give an example of a simple but flexible in-game map system to further help player’s to navigate. We will To simplify development and error-checking we’ll break down the work into bite-size chunks, each building on what came before. For this we’ll make extensive use of the ‘Batch code processor‘_, so you may want to familiarize yourself with that. 1. Planning the map - Here we’ll come up with a small example map to use for the rest of the tutorial.

296 Chapter 13. Tutorials Evennia Documentation, Release 0.9

2. Making a map object - This will showcase how to make a static in-game “map” object a Character could pick up and look at. 3. Building the map areas - Here we’ll actually create the small example area according to the map we designed before. 4. Map code - This will link the map to the location so our output looks something like this:

crossroads(#3) ↑∞↑ ↑ ↑ The merger of two roads. To the north looms a mighty castle. O O O To the south, the glow of a campfire can be seen. To the east lie ↑ ↑ the vast mountains and to the west is heard the waves of the sea. ↑O↑

Exits: north(#8), east(#9), south(#10), west(#11)

We will henceforth assume your game folder is name named mygame and that you haven’t modified the default commands. We will also not be using Colors for our map since they don’t show in the documentation wiki.

13.8.2 Planning the Map

Let’s begin with the fun part! Maps in MUDs come in many different shapes and sizes. Some appear as just boxes connected by lines. Others have complex graphics that are external to the game itself. Our map will be in-game text but that doesn’t mean we’re restricted to the normal alphabet! If you’ve ever selected the Wingdings font in Microsoft Word you will know there are a multitude of other characters around to use. When cre- ating your game with Evennia you have access to the UTF-8 which put at your disposal thousands of letters, number and geometric shapes. For this exercise, we’ve copy-and-pasted from the pallet of special characters used over at Dwarf Fortress to create what is hopefully a pleasing and easy to understood landscape:

↑↑↑↑↑ ↑↑ Places the account can visit are indicated by "O". ↑O↑ Up the top is a castle visitable by the account. ↑∞↑ To the right is a cottage and to the left the beach. ↑ ↑ And down the bottom is a camp site with tents. O O O In the center is the starting location, a crossroads ↑ ↑ which connect the four other areas. ↑O↑ ↑↑↑↑ ↑↑↑↑↑

There are many considerations when making a game map depending on the play style and requirements you intend to implement. Here we will display a 5x5 character map of the area surrounding the account. This means making sure to account for 2 characters around every visitable location. Good planning at this stage can solve many problems before they happen.

13.8.3 Creating a Map Object

In this section we will try to create an actual “map” object that an account can pick up and look at. Evennia offers a range of default commands for creating objects and rooms in-game. While readily accessible, these commands are made to do very specific, restricted things and will thus not offer as much flexibility to experiment (for an advanced exception see in-line functions). Additionally, entering long descriptions and properties over and over in

13.8. Static In Game Map 297 Evennia Documentation, Release 0.9

the game client can become tedious; especially when testing and you may want to delete and recreate things over and over. To overcome this, Evennia offers batch processors that work as input-files created out-of-game. In this tutorial we’ll be using the more powerful of the two available batch processors, the ‘Batch Code Processor‘_, called with the @batchcode command. This is a very powerful tool. It allows you to craft Python files to act as blueprints of your entire game world. These files have access to use Evennia’s Python API directly. Batchcode allows for easy editing and creation in whatever text editor you prefer, avoiding having to manually build the world line-by-line inside the game. Important warning: @batchcode’s power is only rivaled by the @py command. Batchcode is so powerful it should be reserved only for the superuser. Think carefully before you let others (such as Developer- level staff) run @batchcode on their own - make sure you are okay with them running arbitrary Python code on your server. While a simple example, the map object it serves as good way to try out @batchcode. Go to mygame/world and create a new file there named batchcode_map.py:

# mygame/world/batchcode_map.py

from evennia import create_object from evennia import DefaultObject

# We use the create_object function to call into existence a # DefaultObject named "Map" wherever you are standing.

map= create_object(DefaultObject, key="Map", location=caller.location)

# We then access its description directly to make it our map.

map.db.desc= """ ↑↑↑↑↑ ↑↑ ↑O↑ ↑∞↑ ↑ ↑ O O O ↑ ↑ ↑O↑ ↑↑↑↑ ↑↑↑↑↑ """

# This message lets us know our map was created successfully. caller.msg("A map appears out of thin air and falls to the ground.")

Log into your game project as the superuser and run the command

@batchcode batchcode_map

This will load your batchcode_map.py file and execute the code (Evennia will look in your world/ folder automatically so you don’t need to specify it). A new map object should have appeared on the ground. You can view the map by using look map. Let’s take it with the get map command. We’ll need it in case we get lost!

298 Chapter 13. Tutorials Evennia Documentation, Release 0.9

13.8.4 Building the map areas

We’ve just used batchcode to create an object useful for our adventures. But the locations on that map does not actually exist yet - we’re all mapped up with nowhere to go! Let’s use batchcode to build a game area based on our map. We have five areas outlined: a castle, a cottage, a campsite, a coastal beach and the crossroads which connects them. Create a new batchcode file for this in mygame/world, named batchcode_world.py.

# mygame/world/batchcode_world.py from evennia import create_object, search_object from typeclasses import rooms, exits

# We begin by creating our rooms so we can detail them later. centre= create_object(rooms.Room, key="crossroads") north= create_object(rooms.Room, key="castle") east= create_object(rooms.Room, key="cottage") south= create_object(rooms.Room, key="camp") west= create_object(rooms.Room, key="coast")

# This is where we set up the cross roads. # The rooms description is what we see with the 'look' command. centre.db.desc= """ The merger of two roads. A single lamp post dimly illuminates the lonely crossroads. To the north looms a mighty castle. To the south the glow of a campfire can be seen. To the east lie a wall of mountains and to the west the dull roar of the open sea. """

# Here we are creating exits from the centre "crossroads" location to # destinations to the north, east, south, and west. We will be able # to use the exit by typing it's key e.g. "north" or an alias e.g. "n". centre_north= create_object(exits.Exit, key="north", aliases=["n"], location=centre, destination=north) centre_east= create_object(exits.Exit, key="east", aliases=["e"], location=centre, destination=east) centre_south= create_object(exits.Exit, key="south", aliases=["s"], location=centre, destination=south) centre_west= create_object(exits.Exit, key="west", aliases=["w"], location=centre, destination=west)

# Now we repeat this for the other rooms we'll be implementing. # This is where we set up the northern castle. north.db.desc="An impressive castle surrounds you."\ "There might be a princess in one of these towers." north_south= create_object(exits.Exit, key="south", aliases=["s"], location=north, destination=centre)

# This is where we set up the eastern cottage. east.db.desc="A cosy cottage nestled among mountains"\ "stretching east as far as the eye can see." east_west= create_object(exits.Exit, key="west", aliases=["w"], location=east, destination=centre)

# This is where we set up the southern camp. (continues on next page)

13.8. Static In Game Map 299 Evennia Documentation, Release 0.9

(continued from previous page) south.db.desc="Surrounding a clearing are a number of"\ "tribal tents and at their centre a roaring fire." south_north= create_object(exits.Exit, key="north", aliases=["n"], location=south, destination=centre)

# This is where we set up the western coast. west.db.desc="The dark forest halts to a sandy beach."\ "The sound of crashing waves calms the soul." west_east= create_object(exits.Exit, key="east", aliases=["e"], location=west, destination=centre)

# Lastly, lets make an entrance to our world from the default Limbo room. limbo= search_object('Limbo')[0] limbo_exit= create_object(exits.Exit, key="enter world", aliases=["enter"], location=limbo, destination=centre)

Apply this new batch code with @batchcode batchcode_world. If there are no errors in the code we now have a nice mini-world to explore. Remember that if you get lost you can look at the map we created!

13.8.5 In-game minimap

Now we have a landscape and matching map, but what we really want is a mini-map that displays whenever we move to a room or use the look command. We could manually enter a part of the map into the description of every room like we did our map object description. But some MUDs have tens of thousands of rooms! Besides, if we ever changed our map we would have to potentially alter a lot of those room descriptions manually to match the change. So instead we will make one central module to hold our map. Rooms will reference this central location on creation and the map changes will thus come into effect when next running our batchcode. To make our mini-map we need to be able to cut our full map into parts. To do this we need to put it in a format which allows us to do that easily. Luckily, python allows us to treat strings as lists of characters allowing us to pick out the characters we need. mygame/world/map_module.py

# We place our map into a sting here. world_map= """ \ ↑↑↑↑↑ ↑↑ ↑O↑ ↑∞↑ ↑ ↑ O O O ↑ ↑ ↑O↑ ↑↑↑↑ ↑↑↑↑↑ """

# This turns our map string into a list of rows. Because python # allows us to treat strings as a list of characters, we can access # those characters with world_map[5][5] where world_map[row][column]. (continues on next page)

300 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) world_map= world_map.split(' \n')

def return_map(): """ This function returns the whole map """ map=""

#For each row in our map, add it to map for valuey in world_map: map+= valuey map+=" \n"

return map

def return_minimap(x, y, radius=2): """ This function returns only part of the map. Returning all chars in a 2 char radius from (x,y) """ map=""

#For each row we need, add the characters we need. for valuey in world_map[y-radius:y+radius+1]: for valuex in valuey[x-radius:x+radius+1]: map+= valuex map+=" \n"

return map

With our map_module set up, let’s replace our hardcoded map in mygame/world/batchcode_map.py with a reference to our map module. Make sure to import our map_module!

# mygame/world/batchcode_map.py

from evennia import create_object from evennia import DefaultObject from world import map_module

map= create_object(DefaultObject, key="Map", location=caller.location)

map.db.desc= map_module.return_map()

caller.msg("A map appears out of thin air and falls to the ground.")

Log into Evennia as the superuser and run this batchcode. If everything worked our new map should look exactly the same as the old map - you can use @delete to delete the old one (use a number to pick which to delete). Now, lets turn our attention towards our game’s rooms. Let’s use the return_minimap method we created above in order to include a minimap in our room descriptions. This is a little more complicated. By itself we would have to settle for either the map being above the description with room.db.desc = map_string + description_string, or the map going below by reversing their order. Both options are rather unsatisfactory - we would like to have the map next to the text! For this solution we’ll explore the utilities that ship with Evennia. Tucked away in evennia\evennia\utils is a little module called EvTable . This is an advanced ASCII table creator for you to utilize in your game. We’ll use it by creating a basic table with 1 row and two columns (one for our map and one for our text) whilst also hiding the borders. Open the batchfile again

13.8. Static In Game Map 301 Evennia Documentation, Release 0.9

# mygame\world\batchcode_world.py

# Add to imports from evennia.utils import evtable from world import map_module

# [...]

# Replace the descriptions with the below code.

# The cross roads. # We pass what we want in our table and EvTable does the rest. # Passing two arguments will create two columns but we could add more. # We also specify no border. centre.db.desc= evtable.EvTable(map_module.return_minimap(4,5), "The merger of two roads. A single lamp post dimly"\ "illuminates the lonely crossroads. To the north"\ "looms a mighty castle. To the south the glow of"\ "a campfire can be seen. To the east lie a wall of"\ "mountains and to the west the dull roar of the open sea.", border=None) # EvTable allows formatting individual columns and cells. We use that here # to set a maximum width for our description, but letting the map fill # whatever space it needs. centre.db.desc.reformat_column(1, width=70)

# [...]

# The northern castle. north.db.desc= evtable.EvTable(map_module.return_minimap(4,2), "An impressive castle surrounds you. There might be"\ "a princess in one of these towers.", border=None) north.db.desc.reformat_column(1, width=70)

# [...]

# The eastern cottage. east.db.desc= evtable.EvTable(map_module.return_minimap(6,5), "A cosy cottage nestled among mountains stretching"\ "east as far as the eye can see.", border=None) east.db.desc.reformat_column(1, width=70)

# [...]

# The southern camp. south.db.desc= evtable.EvTable(map_module.return_minimap(4,7), "Surrounding a clearing are a number of tribal tents"\ "and at their centre a roaring fire.", border=None) south.db.desc.reformat_column(1, width=70)

# [...]

# The western coast. west.db.desc= evtable.EvTable(map_module.return_minimap(2,5),

(continues on next page)

302 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) "The dark forest halts to a sandy beach. The sound of"\ "crashing waves calms the soul.", border=None) west.db.desc.reformat_column(1, width=70)

Before we run our new batchcode, if you are anything like me you would have something like 100 maps lying around and 3-4 different versions of our rooms extending from limbo. Let’s wipe it all and start with a clean slate. In Command Prompt you can run evennia flush to clear the database and start anew. It won’t reset dbref values however, so if you are at #100 it will start from there. Alternatively you can navigate to mygame/server and delete the evennia.db3 file. Now in Command Prompt use evennia migrate to have a completely freshly made database. Log in to evennia and run @batchcode batchcode_world and you’ll have a little world to explore.

13.8.6 Conclusions

You should now have a mapped little world and a basic understanding of batchcode, EvTable and how easily new game defining features can be added to Evennia. You can easily build from this tutorial by expanding the map and creating more rooms to explore. Why not add more features to your game by trying other tutorials: Add weather to your world, fill your world with NPC’s or implement a combat system.

13.9 Dynamic In Game Map

13.9.1 Introduction

An often desired feature in a MUD is to show an in-game map to help navigation. The Static in-game map tutorial solves this by creating a static map, meaning the map is pre-drawn once and for all - the rooms are then created to match that map. When walking around, parts of the static map is then cut out and displayed next to the room description. In this tutorial we’ll instead do it the other way around; We will dynamically draw the map based on the relationships we find between already existing rooms.

13.9.2 The Grid of Rooms

There are at least two requirements needed for this tutorial to work. 1. The structure of your mud has to follow a logical layout. Evennia supports the layout of your world to be ‘logically’ impossible with rooms looping to themselves or exits leading to the other side of the map. Exits can also be named anything, from “jumping out the window” to “into the fifth dimension”. This tutorial assumes you can only move in the cardinal directions (N, E, S and W). 2. Rooms must be connected and linked together for the map to be generated correctly. Vanilla Evennia comes with a admin command @tunnel that allows a user to create rooms in the cardinal directions, but additional work is needed to assure that rooms are connected. For example, if you @tunnel east and then immediately do @tunnel west you’ll find that you have created two completely stand-alone rooms. So care is needed if you want to create a “logical” layout. In this tutorial we assume you have such a grid of rooms that we can generate the map from.

13.9. Dynamic In Game Map 303 Evennia Documentation, Release 0.9

13.9.3 Concept

Before getting into the code, it is beneficial to understand and conceptualize how this is going to work. The idea is analogous to a worm that starts at your current position. It chooses a direction and ‘walks’ outward from it, mapping its route as it goes. Once it has traveled a pre-set distance it stops and starts over in another direction. An important note is that we want a system which is easily callable and not too complicated. Therefore we will wrap this entire code into a custom Python class (not a typeclass as this doesn’t use any core objects from evennia itself). We are going to create something that displays like this when you type ‘look’:

Hallway

[.][.] [@][.][.][.][.] [.][.][.]

The distant echoes of the forgotten wail throughout the empty halls.

Exits: North, East, South

Your current location is defined by [@] while the [.]s are other rooms that the “worm” has seen since departing from your location.

13.9.4 Setting up the Map Display

First we must define the components for displaying the map. For the “worm” to know what symbol to draw on the map we will have it check an Attribute on the room it visits called sector_type. For this tutorial we understand two symbols - a normal room and the room with us in it. We also define a fallback symbol for rooms without said Attribute - that way the map will still work even if we didn’t prepare the room correctly. Assuming your game folder is named mygame, we create this code in mygame/world/map.py.

# in mygame/world/map.py

# the symbol is identified with a key "sector_type" on the # Room. Keys None and "you" must always exist. SYMBOLS={ None :'.', # for rooms without sector_type Attribute 'you':'[@]', 'SECT_INSIDE':'[.]'}

Since trying to access an unset Attribute returns None, this means rooms without the sector_type Atttribute will show as .. Next we start building the custom class Map. It will hold all methods we need.

# in mygame/world/map.py class Map(object):

def __init__(self, caller, max_width=9, max_length=9): self.caller= caller self.max_width= max_width self.max_length= max_length self.worm_has_mapped={} self.curX= None self.curY= None

• self.caller is normally your Character object, the one using the map.

304 Chapter 13. Tutorials Evennia Documentation, Release 0.9

• self.max_width/length determine the max width and length of the map that will be generated. Note that it’s important that these variables are set to odd numbers to make sure the display area has a center point. • self.worm_has_mapped is building off the worm analogy above. This dictionary will store all rooms the “worm” has mapped as well as its relative position within the grid. This is the most important variable as it acts as a ‘checker’ and ‘address book’ that is able to tell us where the worm has been and what it has mapped so far. • self.curX/Y are coordinates representing the worm’s current location on the grid. Before any sort of mapping can actually be done we need to create an empty display area and do some sanity checks on it by using the following methods.

# in mygame/world/map.py class Map(object): # [... continued]

def create_grid(self): # This method simply creates an empty grid/display area # with the specified variables from __init__(self): board=[] for row in range(self.max_width): board.append([]) for column in range(self.max_length): board[row].append('') return board

def check_grid(self): # this method simply checks the grid to make sure # that both max_l and max_w are odd numbers. return True if self.max_length%2 !=0 or self.max_width%2 !=0\ else False

Before we can set our worm on its way, we need to know some of the computer science behind all this called ‘Graph Traversing’. In Pseudo code what we are trying to accomplish is this:

# pseudo code def draw_room_on_map(room, max_distance): self.draw(room)

if max_distance ==0: return

for exit in room.exits: if self.has_drawn(exit.destination): # skip drawing if we already visited the destination continue else: # first time here! self.draw_room_on_map(exit.destination, max_distance-1)

The beauty of Python is that our actual code of doing this doesn’t differ much if at all from this Pseudo code example. • max_distance is a variable indicating to our Worm how many rooms AWAY from your current location will it map. Obviously the larger the number the more time it will take if your current location has many many rooms around you. The first hurdle here is what value to use for ‘max_distance’. There is no reason for the worm to travel further than what is actually displayed to you. For example, if your current location is placed in the center of a display area of size

13.9. Dynamic In Game Map 305 Evennia Documentation, Release 0.9 max_length = max_width = 9, then the worm need only go 4 spaces in either direction:

[.][.][.][.][@][.][.][.][.] 432101234

The max_distance can be set dynamically based on the size of the display area. As your width/length changes it becomes a simple algebraic linear relationship which is simply max_distance = (min(max_width, max_length) -1) / 2.

13.9.5 Building the Mapper

Now we can start to fill our Map object with some methods. We are still missing a few methods that are very important: • self.draw(self, room) - responsible for actually drawing room to grid. • self.has_drawn(self, room) - checks to see if the room has been mapped and worm has already been here. • self.median(self, number) - a simple utility method that finds the median (middle point) from 0, n • self.update_pos(self, room, exit_name) - updates the worm’s physical position by reassigning self.curX/Y. .accordingly • self.start_loc_on_grid(self) - the very first initial draw on the grid representing your location in the middle of the grid • ’self.show_map- after everything is done convert the map into a readable string • self.draw_room_on_map(self, room, max_distance) - the main method that ties it all to- gether.‘ Now that we know which methods we need, let’s refine our initial __init__(self) to pass some conditional statements and set it up to start building the display.

#mygame/world/map.py class Map(object):

def __init__(self, caller, max_width=9, max_length=9): self.caller= caller self.max_width= max_width self.max_length= max_length self.worm_has_mapped={} self.curX= None self.curY= None

if self.check_grid(): # we have to store the grid into a variable self.grid= self.create_grid() # we use the algebraic relationship self.draw_room_on_map(caller.location, ((min(max_width, max_length)-1)/2)

Here we check to see if the parameters for the grid are okay, then we create an empty canvas and map our initial location as the first room! As mentioned above, the code for the self.draw_room_on_map() is not much different than the Pseudo code. The method is shown below:

306 Chapter 13. Tutorials Evennia Documentation, Release 0.9

# in mygame/world/map.py, in the Map class def draw_room_on_map(self, room, max_distance): self.draw(room)

if max_distance ==0: return

for exit in room.exits: if exit.name not in ("north","east","west","south"): # we only map in the cardinal directions. Mapping up/down would be # an interesting learning project for someone who wanted to try it. continue if self.has_drawn(exit.destination): # we've been to the destination already, skip ahead. continue

self.update_pos(room, exit.name.lower()) self.draw_room_on_map(exit.destination, max_distance-1)

The first thing the “worm” does is to draw your current location in self.draw. Lets define that. . .

#in mygame/word/map.py, in the Map class def draw(self, room): # draw initial ch location on map first! if room == self.caller.location: self.start_loc_on_grid() self.worm_has_mapped[room]=[self.curX, self.curY] else: # map all other rooms self.worm_has_mapped[room]=[self.curX, self.curY] # this will use the sector_type Attribute or None if not set. self.grid[self.curX][self.curY]= SYMBOLS[room.db.sector_type]

In self.start_loc_on_grid(): def median(self, num): lst= sorted(range(0, num)) n= len(lst) m=n-1 return (lst[n//2]+ lst[m//2])/ 2.0 def start_loc_on_grid(self): x= self.median(self.max_width) y= self.median(self.max_length) # x and y are floats by default, can't index lists with float types x, y= int(x), int(y)

self.grid[x][y]= SYMBOLS['you'] self.curX, self.curY= x, y # updating worms current location

After the system has drawn the current map it checks to see if the max_distance is 0 (since this is the inital start phase it is not). Now we handle the iteration once we have each individual exit in the room. The first thing it does is check if the room the Worm is in has been mapped already.. lets define that. . .

13.9. Dynamic In Game Map 307 Evennia Documentation, Release 0.9

def has_drawn(self, room): return True if room in self.worm_has_mapped.keys() else False

If has_drawn returns False that means the worm has found a room that hasn’t been mapped yet. It will then ‘move’ there. The self.curX/Y sort of lags behind, so we have to make sure to track the position of the worm; we do this in self.update_pos() below.

def update_pos(self, room, exit_name): # this ensures the coordinates stays up to date # to where the worm is currently at. self.curX, self.curY=\ self.worm_has_mapped[room][0], self.worm_has_mapped[room][1]

# now we have to actually move the pointer # variables depending on which 'exit' it found if exit_name =='east': self.curY+=1 elif exit_name =='west': self.curY-=1 elif exit_name =='north': self.curX-=1 elif exit_name =='south': self.curX+=1

Once the system updates the position of the worm it feeds the new room back into the original draw_room_on_map() and starts the process all over again.. That is essentially the entire thing. The final method is to bring it all together and make a nice presentational string out of it using the self.show_map() method.

def show_map(self): map_string="" for row in self.grid: map_string+="".join(row) map_string+=" \n"

return map_string

13.9.6 Using the Map

In order for the map to get triggered we store it on the Room typeclass. If we put it in return_appearance we will get the map back every time we look at the room. return_appearance is a default Evennia hook available on all objects; it is called e.g. by the look command to get the description of something (the room in this case).

# in mygame/typeclasses/rooms.py

from evennia import DefaultRoom from world.map import Map

class Room(DefaultRoom):

def return_appearance(self, looker): # [...] string=" %s\n"% Map(looker).show_map() (continues on next page)

308 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) # Add all the normal stuff like room description, # contents, exits etc. string+=" \n"+ super().return_appearance(looker) return string

Obviously this method of generating maps doesn’t take into account of any doors or exits that are hidden.. etc.. but hopefully it serves as a good base to start with. Like previously mentioned, it is very important to have a solid foundation on rooms before implementing this. You can try this on vanilla evennia by using @tunnel and essentially you can just create a long straight/edgy non-looping rooms that will show on your in-game map. The above example will display the map above the room description. You could also use an EvTable to place descrip- tion and map next to each other. Some other things you can do is to have a Command that displays with a larger radius, maybe with a legend and other features. Below is the whole map.py for your reference. You need to update your Room typeclass (see above) to ac- tually call it. Remember that to see different symbols for a location you also need to set the sector_type Attribute on the room to one of the keys in the SYMBOLS dictionary. So in this example, to make a room be mapped as [.] you would set the room’s sector_type to "SECT_INSIDE". Try it out with @set here/ sector_type = "SECT_INSIDE". If you wanted all new rooms to have a given sector symbol, you could change the default in the SYMBOLS´ dictionary below, or you could add the Attribute in the Room'sat_object_creation‘ method.

#mygame/world/map.py

# These are keys set with the Attribute sector_type on the room. # The keys None and "you" must always exist. SYMBOLS={ None :'.', # for rooms without a sector_type attr 'you':'[@]', 'SECT_INSIDE':'[.]'}

class Map(object):

def __init__(self, caller, max_width=9, max_length=9): self.caller= caller self.max_width= max_width self.max_length= max_length self.worm_has_mapped={} self.curX= None self.curY= None

if self.check_grid(): # we actually have to store the grid into a variable self.grid= self.create_grid() self.draw_room_on_map(caller.location, ((min(max_width, max_length)-1)/2))

def update_pos(self, room, exit_name): # this ensures the pointer variables always # stays up to date to where the worm is currently at. self.curX, self.curY=\ self.worm_has_mapped[room][0], self.worm_has_mapped[room][1]

# now we have to actually move the pointer # variables depending on which 'exit' it found if exit_name =='east': self.curY+=1 (continues on next page)

13.9. Dynamic In Game Map 309 Evennia Documentation, Release 0.9

(continued from previous page) elif exit_name =='west': self.curY-=1 elif exit_name =='north': self.curX-=1 elif exit_name =='south': self.curX+=1

def draw_room_on_map(self, room, max_distance): self.draw(room)

if max_distance ==0: return

for exit in room.exits: if exit.name not in ("north","east","west","south"): # we only map in the cardinal directions. Mapping up/down would be # an interesting learning project for someone who wanted to try it. continue if self.has_drawn(exit.destination): # we've been to the destination already, skip ahead. continue

self.update_pos(room, exit.name.lower()) self.draw_room_on_map(exit.destination, max_distance-1)

def draw(self, room): # draw initial caller location on map first! if room == self.caller.location: self.start_loc_on_grid() self.worm_has_mapped[room]=[self.curX, self.curY] else: # map all other rooms self.worm_has_mapped[room]=[self.curX, self.curY] # this will use the sector_type Attribute or None if not set. self.grid[self.curX][self.curY]= SYMBOLS[room.db.sector_type]

def median(self, num): lst= sorted(range(0, num)) n= len(lst) m=n-1 return (lst[n//2]+ lst[m//2])/ 2.0

def start_loc_on_grid(self): x= self.median(self.max_width) y= self.median(self.max_length) # x and y are floats by default, can't index lists with float types x, y= int(x), int(y)

self.grid[x][y]= SYMBOLS['you'] self.curX, self.curY= x, y # updating worms current location

def has_drawn(self, room): return True if room in self.worm_has_mapped.keys() else False

def create_grid(self): (continues on next page)

310 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) # This method simply creates an empty grid # with the specified variables from __init__(self): board=[] for row in range(self.max_width): board.append([]) for column in range(self.max_length): board[row].append('') return board

def check_grid(self): # this method simply checks the grid to make sure # both max_l and max_w are odd numbers return True if self.max_length%2 !=0 or \ self.max_width%2 !=0 else False

def show_map(self): map_string="" for row in self.grid: map_string+="".join(row) map_string+=" \n"

return map_string

13.9.7 Final Comments

The Dynamic map could be expanded with further capabilities. For example, it could mark exits or allow NE, SE etc directions as well. It could have colors for different terrain types. One could also look into up/down directions and figure out how to display that in a good way.

13.10 Implementing a game rule system

The simplest way to create an online roleplaying game (at least from a code perspective) is to simply grab a paperback RPG rule book, get a staff of game masters together and start to run scenes with whomever logs in. Game masters can roll their dice in front of their computers and tell the players the results. This is only one step away from a traditional tabletop game and puts heavy demands on the staff - it is unlikely staff will be able to keep up around the clock even if they are very dedicated.

Many games, even the most roleplay-dedicated, thus tend to allow for players to mediate themselves to some extent. A common way to do this is to introduce coded systems - that is, to let the computer do some of the heavy lifting. A basic thing is to add an online dice-roller so everyone can make rolls and make sure noone is cheating. Somewhere at this level you find the most bare-bones roleplaying MUSHes.

The advantage of a coded system is that as long as the rules are fair the computer is too - it makes no judgement calls and holds no personal grudges (and cannot be accused of holding any). Also, the computer doesn’t need to sleep and can always be online regardless of when a player logs on. The drawback is that a coded system is not flexible and won’t adapt to the unprogrammed actions human

13.10. Implementing a game rule system 311 Evennia Documentation, Release 0.9 players may come up with in role play. For this reason many roleplay-heavy MUDs do a hybrid variation - they use coded systems for things like combat and skill progression but leave role play to be mostly freeform, overseen by staff game masters.

Finally, on the other end of the scale are less- or no-roleplay games, where game mechanics (and thus player fairness) is the most important aspect. In such games the only events with in-game value are those resulting from code. Such games are very common and include everything from hack-and-slash MUDs to various tactical simulations.

So your first decision needs to be just what type of system you are aiming for. This page will try to give some ideas for how to organize the “coded” part of your system, however big that may be.

13.10.1 Overall system infrastructure

We strongly recommend that you code your rule system as stand-alone as possible. That is, don’t spread your skill check code, race bonus calculation, die modifiers or what have you all over your game.

• Put everything you would need to look up in a rule book into a module in mygame/world. Hide away as much as you can. Think of it as a black box (or maybe the code representation of an all-knowing game master). The rest of your game will ask this black box questions and get answers back. Exactly how it arrives at those results should not need to be known outside the box. Doing it this way makes it easier to change and update things in one place later. • Store only the minimum stuff you need with each game object. That is, if your Characters need 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, 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. Finally you could even go with a custom django model. Which is the better depends on your game and the complexity of your system. • Make a clear API into your rules. That is, make methods/functions that you feed with, say, your Character and which skill you want to check. That is, you want something similar to this:

from world import rules result= rules.roll_skill(character,"hunting") result= rules.roll_challenge(character1, character2,"swords")

You might need to make these functions more or less complex depending on your game. For example the properties of the room might matter to the outcome of a roll (if the room is dark, burning etc). Establishing just what you need to send into your game mechanic module is a great way to also get a feel for what you need to add to your engine.

13.10.2 Coded systems

Inspired by tabletop role playing games, most game systems mimic some sort of die mechanic. To this end Evennia offers a full dice roller in its contrib folder. For custom implementations, Python offers many ways to randomize

312 Chapter 13. Tutorials Evennia Documentation, Release 0.9

a result using its in-built random module. No matter how it’s implemented, we will in this text refer to the action of determining an outcome as a “roll”. In a freeform system, the result of the roll is just compared with values and people (or the game master) just agree on what it means. In a coded system the result now needs to be processed somehow. There are many things that may happen as a result of rule enforcement: • Health may be added or deducted. This can effect the character in various ways. • Experience may need to be added, and if a level-based system is used, the player might need to be informed they have increased a level. • Room-wide effects need to be reported to the room, possibly affecting everyone in the room. There are also a slew of other things that fall under “Coded systems”, including things like weather, NPC artificial intelligence and game economy. Basically everything about the world that a Game master would control in a tabletop role playing game can be mimicked to some level by coded systems.

13.10.3 Example of Rule module

Here is a simple example of a rule module. This is what we assume about our simple example game: • Characters have only four numerical values: – Their level, which starts at 1. – A skill combat, which determines how good they are at hitting things. Starts between 5 and 10. – Their Strength, STR, which determine how much damage they do. Starts between 1 and 10. – Their Health points, HP, which starts at 100. • When a Character reaches HP = 0, they are presumed “defeated”. Their HP is reset and they get a failure message (as a stand-in for death code). • Abilities are stored as simple Attributes on the Character. • “Rolls” are done by rolling a 100-sided die. If the result is below the combat value, it’s a success and damage is rolled. Damage is rolled as a six-sided die + the value of STR (for this example we ignore weapons and assume STR is all that matters). • Every successful attack roll gives 1-3 experience points (XP). Every time the number of XP reaches (level + 1) ** 2, the Character levels up. When leveling up, the Character’s combat value goes up by 2 points and STR by one (this is a stand-in for a real progression system).

Character

The Character typeclass is simple. It goes in mygame/typeclasses/characters.py. There is already an empty Character class there that Evennia will look to and use.

from random import randint from evennia import DefaultCharacter

class Character(DefaultCharacter): """ Custom rule-restricted character. We randomize the initial skill and ability values bettween 1-10. """ def at_object_creation(self): "Called only when first created" (continues on next page)

13.10. Implementing a game rule system 313 Evennia Documentation, Release 0.9

(continued from previous page) self.db.level=1 self.db.HP= 100 self.db.XP=0 self.db.STR= randint(1, 10) self.db.combat= randint(5, 10)

@reload the server to load up the new code. Doing examine self will however not show the new Attributes on yourself. This is because the at_object_creation hook is only called on new Characters. Your Character was already created and will thus not have them. To force a reload, use the following command:

@typeclass/force/reset self

The examine self command will now show the new Attributes.

Rule module

This is a module mygame/world/rules.py. from random import randint def roll_hit(): "Roll 1d100" return randint(1, 100) def roll_dmg(): "Roll 1d6" return randint(1,6) def check_defeat(character): "Checks if a character is'defeated'." if character.db.HP<=0: character.msg("You fall down, defeated!") character.db.HP= 100 # reset def add_XP(character, amount): "Add XP to character, tracking level increases." character.db.XP+= amount if character.db.XP>= (character.db.level+1) ** 2: character.db.level+=1 character.db.STR+=1 character.db.combat+=2 character.msg("You are now level %i!"% character.db.level) def skill_combat(*args): """ This determines outcome of combat. The one who rolls under their combat skill AND higher than their opponent's roll hits. """ char1, char2= args roll1, roll2= roll_hit(), roll_hit() failtext="You are hit by %s for %i damage!" wintext="You hit %s for %i damage!" xp_gain= randint(1,3) if char1.db.combat>= roll1> roll2: (continues on next page)

314 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) # char 1 hits dmg= roll_dmg()+ char1.db.STR char1.msg(wintext% (char2, dmg)) add_XP(char1, xp_gain) char2.msg(failtext% (char1, dmg)) char2.db.HP-= dmg check_defeat(char2) elif char2.db.combat>= roll2> roll1: # char 2 hits dmg= roll_dmg()+ char2.db.STR char1.msg(failtext% (char2, dmg)) char1.db.HP-= dmg check_defeat(char1) char2.msg(wintext% (char1, dmg)) add_XP(char2, xp_gain) else: # a draw drawtext="Neither of you can find an opening." char1.msg(drawtext) char2.msg(drawtext)

SKILLS={"combat": skill_combat} def roll_challenge(character1, character2, skillname): """ Determine the outcome of a skill challenge between two characters based on the skillname given. """ if skillname in SKILLS: SKILLS[skillname](character1, character2) else: raise RunTimeError("Skillname %s not found."% skillname)

These few functions implement the entirety of our simple rule system. We have a function to check the “defeat” condition and reset the HP back to 100 again. We define a generic “skill” function. Multiple skills could all be added with the same signature; our SKILLS dictionary makes it easy to look up the skills regardless of what their actual functions are called. Finally, the access function roll_challenge just picks the skill and gets the result. In this example, the skill function actually does a lot - it not only rolls results, it also informs everyone of their results via character.msg() calls. Here is an example of usage in a game command: from evennia import Command from world import rules class CmdAttack(Command): """ attack an opponent

Usage: attack

This will attack a target in the same room, dealing damage with your bare hands. """ def func(self): (continues on next page)

13.10. Implementing a game rule system 315 Evennia Documentation, Release 0.9

(continued from previous page) "Implementing combat"

caller= self.caller if not self.args: caller.msg("You need to pick a target to attack.") return

target= caller.search(self.args) if target: rules.roll_challenge(caller, target,"combat")

Note how simple the command becomes and how generic you can make it. It becomes simple to offer any number of Combat commands by just extending this functionality - you can easily roll challenges and pick different skills to check. And if you ever decided to, say, change how to determine hit chance, you don’t have to change every command, but need only change the single roll_hit function inside your rules module.

13.11 Turn based Combat System

This tutorial gives an example of a full, if simplified, combat system for Evennia. It was inspired by the discussions held on the mailing list.

13.11.1 Overview of combat system concepts

Most MUDs will use some sort of combat system. There are several main variations: • Freeform - the simplest form of combat to implement, common to MUSH-style roleplaying games. This means the system only supplies dice rollers or maybe commands to compare skills and spit out the result. Dice rolls are done to resolve combat according to the rules of the game and to direct the scene. A game master may be required to resolve rule disputes. • Twitch - This is the traditional MUD hack&slash style combat. In a twitch system there is often no difference between your normal “move-around-and-explore mode” and the “combat mode”. You enter an attack command and the system will calculate if the attack hits and how much damage was caused. Normally attack commands have some sort of timeout or notion of recovery/balance to reduce the advantage of spamming or client scripting. Whereas the simplest systems just means entering kill over and over, more sophisticated twitch systems include anything from defensive stances to tactical positioning. • Turn-based - a turn based system means that the system pauses to make sure all combatants can choose their ac- tions before continuing. In some systems, such entered actions happen immediately (like twitch-based) whereas in others the resolution happens simultaneously at the end of the turn. The disadvantage of a turn-based system is that the game must switch to a “combat mode” and one also needs to take special care of how to handle new combatants and the passage of time. The advantage is that success is not dependent on typing speed or of setting up quick client macros. This potentially allows for emoting as part of combat which is an advantage for roleplay-heavy games. To implement a freeform combat system all you need is a dice roller and a roleplaying rulebook. See contrib/dice.py for an example dice roller. To implement at twitch-based system you basically need a few combat commands, possibly ones with a cooldown. You also need a game rule module that makes use of it. We will focus on the turn-based variety here.

316 Chapter 13. Tutorials Evennia Documentation, Release 0.9

13.11.2 Tutorial overview

This tutorial will implement the slightly more complex turn-based combat system. Our example has the following properties: • Combat is initiated with attack , this initiates the combat mode. • Characters may join an ongoing battle using attack against a character already in combat. • Each turn every combating character will get to enter two commands, their internal order matters and they are compared one-to-one in the order given by each combatant. Use of say and pose is free. • The commands are (in our example) simple; they can either hit , feint or parry . They can also defend, a generic passive defense. Finally they may choose to disengage/ flee. • When attacking we use a classic rock-paper-scissors mechanic to determine success: hit defeats feint, which defeats parry which defeats hit. defend is a general passive action that has a percentage chance to win against hit (only). • disengage/flee must be entered two times in a row and will only succeed if there is no hit against them in that time. If so they will leave combat mode. • Once every player has entered two commands, all commands are resolved in order and the result is reported. A new turn then begins. • If players are too slow the turn will time out and any unset commands will be set to defend. 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 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 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. We will only sketch such a module here for our end-turn combat resolution. • An attack command 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.

13.11.3 The combat handler

The combat handler is implemented as a stand-alone Script. 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 need to disengage from the first combat before they could join another). The reason we don’t store this Script “on” any specific character is because any character may leave the combat at any time. Instead the script holds references to all characters involved in the combat. Vice-versa, all characters holds a back-reference to the current combat handler. While we don’t use this very much here this might allow the combat commands on the characters to access and 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‘_. 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.

13.11. Turn based Combat System 317 Evennia Documentation, Release 0.9

Here is a basic combat handler. Assuming our game folder is named mygame, we store it in mygame/ typeclasses/combat_handler.py:

# mygame/typeclasses/combat_handler.py import random from evennia import DefaultScript from world.rules import resolve_combat class CombatHandler(DefaultScript): """ This implements the combat handler. """

# standard Script hooks

def at_script_creation(self): "Called when script is first created"

self.key="combat_handler_ %i"% random.randint(1, 1000) self.desc="handles combat" self.interval= 60 * 2 # two minute timeout self.start_delay= True self.persistent= True

# store all combatants self.db.characters={} # store all actions for each turn self.db.turn_actions={} # number of actions entered per combatant self.db.action_count={}

def _init_character(self, character): """ This initializes handler back-reference and combat cmdset on a character """ character.ndb.combat_handler= self character.cmdset.add("commands.combat.CombatCmdSet")

def _cleanup_character(self, character): """ Remove character from handler and clean it of the back-reference and cmdset """ dbref= character.id del self.db.characters[dbref] del self.db.turn_actions[dbref] del self.db.action_count[dbref] del character.ndb.combat_handler character.cmdset.delete("commands.combat.CombatCmdSet")

def at_start(self): """ This is called on first start but also when the script is restarted after a server reboot. We need to re-assign this combat handler to all characters as well as re-assign the cmdset. """ (continues on next page)

318 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) for character in self.db.characters.values(): self._init_character(character)

def at_stop(self): "Called just before the script is stopped/destroyed." for character in list(self.db.characters.values()): # note: the list() call above disconnects list from database self._cleanup_character(character)

def at_repeat(self): """ This is called every self.interval seconds (turn timeout) or when force_repeat is called (because everyone has entered their commands). We know this by checking the existence of the `normal_turn_end` NAttribute, set just before calling force_repeat.

""" if self.ndb.normal_turn_end: # we get here because the turn ended normally # (force_repeat was called) - no msg output del self.ndb.normal_turn_end else: # turn timeout self.msg_all("Turn timer timed out. Continuing.") self.end_turn()

# Combat-handler methods

def add_character(self, character): "Add combatant to handler" dbref= character.id self.db.characters[dbref]= character self.db.action_count[dbref]=0 self.db.turn_actions[dbref]=[("defend", character, None), ("defend", character, None)] # set up back-reference self._init_character(character)

def remove_character(self, character): "Remove combatant from handler" if character.id in self.db.characters: self._cleanup_character(character) if not self.db.characters: # if no more characters in battle, kill this handler self.stop()

def msg_all(self, message): "Send message to all combatants" for character in self.db.characters.values(): character.msg(message)

def add_action(self, action, character, target): """ Called by combat commands to register an action with the handler.

action - string identifying the action, like "hit" or "parry" (continues on next page)

13.11. Turn based Combat System 319 Evennia Documentation, Release 0.9

(continued from previous page) character - the character performing the action target - the target character or None

actions are stored in a dictionary keyed to each character, each of which holds a list of max 2 actions. An action is stored as a tuple (character, action, target). """ dbref= character.id count= self.db.action_count[dbref] if 0<= count<=1: # only allow 2 actions self.db.turn_actions[dbref][count]= (action, character, target) else: # report if we already used too many actions return False self.db.action_count[dbref]+=1 return True

def check_end_turn(self): """ Called by the command to eventually trigger the resolution of the turn. We check if everyone has added all their actions; if so we call force the script to repeat immediately (which will call `self.at_repeat()` while resetting all timers). """ if all(count>1 for count in self.db.action_count.values()): self.ndb.normal_turn_end= True self.force_repeat()

def end_turn(self): """ This resolves all actions by calling the rules module. It then resets everything and starts the next turn. It is called by at_repeat(). """ resolve_combat(self, self.db.turn_actions)

if len(self.db.characters)<2: # less than 2 characters in battle, kill this handler self.msg_all("Combat has ended") self.stop() else: # reset counters before next turn for character in self.db.characters.values(): self.db.characters[character.id]= character self.db.action_count[character.id]=0 self.db.turn_actions[character.id]=[("defend", character, None), ("defend", character, None)] self.msg_all("Next turn begins ...")

This implements all the useful properties of our combat handler. This Script will survive a reboot and will automati- cally re-assert itself when it comes back online. Even the current state of the combat should be unaffected since it is saved in Attributes at every turn. An important part to note is the use of the Script’s standard at_repeat hook and the force_repeat method to end each turn. This allows for everything to go through the same mechanisms with minimal repetition of code. What is not present in this handler is a way for players to view the actions they set or to change their actions once they

320 Chapter 13. Tutorials Evennia Documentation, Release 0.9 have been added (but before the last one has added theirs). We leave this as an exercise.

13.11.4 Combat commands

Our combat commands - the commands that are to be available to us during the combat - are (in our example) very simple. In a full implementation the commands available might be determined by the weapon(s) held by the player or by which skills they know. We create them in mygame/commands/combat.py.

# mygame/commands/combat.py from evennia import Command class CmdHit(Command): """ hit an enemy

Usage: hit

Strikes the given enemy with your current weapon. """ key="hit" aliases=["strike","slash"] help_category="combat"

def func(self): "Implements the command" if not self.args: self.caller.msg("Usage: hit ") return target= self.caller.search(self.args) if not target: return ok= self.caller.ndb.combat_handler.add_action("hit", self.caller, target) if ok: self.caller.msg("You add'hit' to the combat queue") else: self.caller.msg("You can only queue two actions per turn!")

# tell the handler to check if turn is over self.caller.ndb.combat_handler.check_end_turn()

The other commands CmdParry, CmdFeint, CmdDefend and CmdDisengage look basically the same. We should also add a custom help command to list all the available combat commands and what they do. We just need to put them all in a cmdset. We do this at the end of the same module:

# mygame/commands/combat.py from evennia import CmdSet from evennia import default_cmds class CombatCmdSet(CmdSet): (continues on next page)

13.11. Turn based Combat System 321 Evennia Documentation, Release 0.9

(continued from previous page) key="combat_cmdset" mergetype="Replace" priority= 10 no_exits= True

def at_cmdset_creation(self): self.add(CmdHit()) self.add(CmdParry()) self.add(CmdFeint()) self.add(CmdDefend()) self.add(CmdDisengage()) self.add(CmdHelp()) self.add(default_cmds.CmdPose()) self.add(default_cmds.CmdSay())

13.11.5 Rules module

A general way to implement a rule module is found in the rule system tutorial. Proper resolution would likely require us to change our Characters to store things like strength, weapon skills and so on. So for this example we will settle for a very simplistic rock-paper-scissors kind of setup with some randomness thrown in. We will not deal with damage here but just announce the results of each turn. In a real system the Character objects would hold stats to affect their skills, their chosen weapon affect the choices, they would be able to lose health etc. Within each turn, there are “sub-turns”, each consisting of one action per character. The actions within each sub-turn happens simultaneously and only once they have all been resolved we move on to the next sub-turn (or end the full turn). Note: In our simple example the sub-turns don’t affect each other (except for ‘‘disengage/flee‘‘), nor do any effects carry over between turns. The real power of a turn-based system would be to add real tactical possibilities here though; For example if your hit got parried you could be out of balance and your next action would be at a disadvantage. A successful feint would open up for a subsequent attack and so on . . . Our rock-paper-scissor setup works like this: • hit beats feint and flee/disengage. It has a random chance to fail against defend. • parry beats hit. • feint beats parry and is then counted as a hit. • defend does nothing but has a chance to beat hit. • flee/disengage must succeed two times in a row (i.e. not beaten by a hit once during the turn). If so the character leaves combat.

# mygame/world/rules.py import random

# messages def resolve_combat(combat_handler, actiondict): """ This is called by the combat handler actiondict is a dictionary with a list of two actions for each character: {char.id:[(action1, char, target), (action2, char, target)], ...} (continues on next page)

322 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) """ flee={} # track number of flee commands per character for isub in range(2): # loop over sub-turns messages=[] for subturn in (sub[isub] for sub in actiondict.values()): # for each character, resolve the sub-turn action, char, target= subturn if target: taction, tchar, ttarget= actiondict[target.id][isub] if action =="hit": if taction =="parry" and ttarget == char: msg=" %s tries to hit %s, but %s parries the attack!" messages.append(msg% (char, tchar, tchar)) elif taction =="defend" and random.random()< 0.5: msg=" %s defends against the attack by %s." messages.append(msg% (tchar, char)) elif taction =="flee": msg=" %s stops %s from disengaging, with a hit!" flee[tchar]=-2 messages.append(msg% (char, tchar)) else: msg=" %s hits %s, bypassing their %s!" messages.append(msg% (char, tchar, taction)) elif action =="parry": if taction =="hit": msg=" %s parries the attack by %s." messages.append(msg% (char, tchar)) elif taction =="feint": msg=" %s tries to parry, but %s feints and hits!" messages.append(msg% (char, tchar)) else: msg=" %s parries to no avail." messages.append(msg% char) elif action =="feint": if taction =="parry": msg=" %s feints past %s's parry, landing a hit!" messages.append(msg% (char, tchar)) elif taction =="hit": msg=" %s feints but is defeated by %s hit!" messages.append(msg% (char, tchar)) else: msg=" %s feints to no avail." messages.append(msg% char) elif action =="defend": msg=" %s defends." messages.append(msg% char) elif action =="flee": if char in flee: flee[char]+=1 else: flee[char]=1 msg=" %s tries to disengage (two subsequent turns needed)" messages.append(msg% char)

# echo results of each subturn combat_handler.msg_all("\n".join(messages)) (continues on next page)

13.11. Turn based Combat System 323 Evennia Documentation, Release 0.9

(continued from previous page)

# at the end of both sub-turns, test if anyone fled msg=" %s withdraws from combat." for (char, fleevalue) in flee.items(): if fleevalue ==2: combat_handler.msg_all(msg% char) combat_handler.remove_character(char)

To make it simple (and to save space), this example rule module actually resolves each interchange twice - first when it gets to each character and then again when handling the target. Also, since we use the combat handler’s msg_all method here, the system will get pretty spammy. To clean it up, one could imagine tracking all the possible interactions to make sure each pair is only handled and reported once.

13.11.6 Combat initiator command

This is the last component we need, a command to initiate combat. This will tie everything together. We store this with the other combat commands.

# mygame/commands/combat.py from evennia import create_script class CmdAttack(Command): """ initiates combat

Usage: attack

This will initiate combat with . If

def func(self): "Handle command" if not self.args: self.caller.msg("Usage: attack ") return target= self.caller.search(self.args) if not target: return # set up combat if target.ndb.combat_handler: # target is already in combat - join it target.ndb.combat_handler.add_character(self.caller) target.ndb.combat_handler.msg_all("%s joins combat!"% self.caller) else: # create a new combat handler chandler= create_script("combat_handler.CombatHandler") chandler.add_character(self.caller) chandler.add_character(target) self.caller.msg("You attack %s! You are in combat."% target) target.msg("%s attacks you! You are in combat."% self.caller)

324 Chapter 13. Tutorials Evennia Documentation, Release 0.9

The attack command will not go into the combat cmdset but rather into the default cmdset. See e.g. the Adding Command Tutorial if you are unsure about how to do this.

13.11.7 Expanding the example

At this point you should have a simple but flexible turn-based combat system. We have taken several shortcuts and simplifications in this example. The output to the players is likely too verbose during combat and too limited when it comes to informing about things surrounding it. Methods for changing your commands or list them, view who is in combat etc is likely needed - this will require play testing for each game and style. There is also currently no information displayed for other people happening to be in the same room as the combat - some less detailed information should probably be echoed to the room to show others what’s going on.

13.12 Evennia for roleplaying sessions

This tutorial will explain how to set up a realtime or play-by-post tabletop style game using a fresh Evennia server. The scenario is thus: You and a bunch of friends want to play a tabletop role playing game online. One of you will be the game master and you are all okay with playing using written text. You want both the ability to role play in real-time (when people happen to be online at the same time) as well as the ability for people to post when they can and catch up on what happened since they were last online. This is the functionality we will be needing and using: • The ability to make one of you the GM (game master), with special abilities. •A Character sheet that players can create, view and fill in. It can also be locked so only the GM can modify it. •A dice roller mechanism, for whatever type of dice the RPG rules require. • Rooms, to give a sense of location and to compartmentalize play going on- This means both Character move- ments from location to location and GM explicitly moving them around. • Channels, for easily sending text to all subscribing accounts, regardless of location. • Account-to-Account messaging capability, including sending to multiple recipients simultaneously, regardless of location. We will find most of these things are already part of vanilla Evennia, but that we can expand on the defaults for our particular use-case. Below we will flesh out these components from start to finish.

13.12.1 Starting out

We will assume you start from scratch. You need Evennia installed, as per the Getting Started instructions. Initialize a new game directory with evennia init . 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 tutorial, just to see roughly where things are modified.

13.12.2 The Game Master role

Short version

• Simplest way: Being an admin, just give one account Admins permission using the standard @perm command.

13.12. Evennia for roleplaying sessions 325 Evennia Documentation, Release 0.9

• Better but more work: Make a custom command to set/unset the above, while tweaking the Character to show your renewed GM status to the other accounts.

The permission hierarchy

Evennia has the following permission hierarchy out of the box: Players, Helpers, Builders, Admins and finally Devel- opers. 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. 1. Players is the permission set on normal players. This is the default for anyone creating a new account on the server. 2. Helpers are like Players except they also have the ability to create/edit new help entries. This could be granted to players who are willing to help with writing lore or custom logs for everyone. 3. Builders is not used in our case since the GM should be the only world-builder. 4. Admins is the permission level the GM should have. Admins can do everything builders can (create/describe rooms etc) but also kick accounts, rename them and things like that. 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 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

Only Developers can (by default) change permission level. Only they have access to the @perm command:

> @perm Yvonne Permissions on Yvonne: accounts

> @perm Yvonne= Admins > @perm Yvonne Permissions on Yvonne: accounts, admins

> @perm/del Yvonne= Admins > @perm Yvonne Permissions on Yvonne: accounts

There is no need to remove the basic Players permission when adding the higher permission: the highest will be used. Permission level names are not case sensitive. You can also use both plural and singular, so “Admins” gives the same powers as “Admin”.

Optional: Making a GM-granting command

Use of @perm works out of the box, but it’s really the bare minimum. Would it not be nice if other accounts could tell at a glance who the GM is? Also, we shouldn’t really need to remember that the permission level is called “Admins”. It would be easier if we could just do @gm and @notgm and at the same time change something make the new GM status apparent. So let’s make this possible. This is what we’ll do: 1. We’ll customize the default Character class. If an object of this class has a particular flag, its name will have the string(GM) added to the end.

326 Chapter 13. Tutorials Evennia Documentation, Release 0.9

2. We’ll add a new command, for the server admin to assign the GM-flag properly.

Character modification

Let’s first start by customizing the Character. We recommend you browse the beginning of the Account 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”. Open mygame/typeclasses/characters.py and modify the default Character class:

# in mygame/typeclasses/characters.py

# [...]

class Character(DefaultCharacter): # [...] def get_display_name(self, looker, **kwargs): """ This method customizes how character names are displayed. We assume only permissions of types "Developers" and "Admins" require special attention. """ name= self.key selfaccount= self.account # will be None if we are not puppeted lookaccount= looker.account #-"-

if selfaccount and selfaccount.db.is_gm: # A GM. Show name as name(GM) name=" %s(GM)"% name

if lookaccount and \ (lookaccount.permissions.get("Developers") or lookaccount.db.is_gm): # Developers/GMs see name(#dbref) or name(GM)(#dbref) return "%s(#%s)"% (name, self.id) else: return name

Above, we change how the Character’s name is displayed: If the account controlling this Character is 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) 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, that is, it will show only to those in the same location. If we wanted it to also pop up in, say, who listings and channels, we’d need to make a similar change to the Account typeclass in mygame/typeclasses/ accounts.py. We leave this as an exercise to the reader.

New @gm/@ungm command

We will describe in some detail how to create and add an Evennia command 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. Open mygame/commands/command.py and add a new Command class at the bottom:

13.12. Evennia for roleplaying sessions 327 Evennia Documentation, Release 0.9

# in mygame/commands/command.py from evennia import default_cmds

# [...] import evennia class CmdMakeGM(default_cmds.MuxCommand): """ Change an account's GM status

Usage: @gm @ungm

""" # note using the key without @ means both @gm !gm etc will work key="gm" aliases="ungm" locks="cmd:perm(Developers)" help_category="RP"

def func(self): "Implement the command" caller= self.caller

if not self.args: caller.msg("Usage: @gm account or @ungm account") return

accountlist= evennia.search_account(self.args) # returns a list if not accountlist: caller.msg("Could not find account' %s'"% self.args) return elif len(accountlist)>1: caller.msg("Multiple matches for' %s': %s"%(self.args, accountlist)) return else: account= accountlist[0]

if self.cmdstring =="gm": # turn someone into a GM if account.permissions.get("Admins"): caller.msg("Account %s is already a GM."% account) else: account.permissions.add("Admins") caller.msg("Account %s is now a GM."% account) account.msg("You are now a GM (changed by %s)."% caller) account.character.db.is_gm= True else: # @ungm was entered - revoke GM status from someone if not account.permissions.get("Admins"): caller.msg("Account %s is not a GM."% account) else: account.permissions.remove("Admins") caller.msg("Account %s is no longer a GM."% account)

(continues on next page)

328 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) account.msg("You are no longer a GM (changed by %s)."% caller) del account.character.db.is_gm

All the command does is to locate the account target and assign it the Admins permission if we used @gm or revoke it if using the @ungm alias. We also set/unset the is_gm Attribute that is expected by our new Character. get_display_name method from earlier. We could have made this into two separate commands or opted for a syntax like @gm/revoke . Instead we examine how this command was called (stored in self.cmdstring) in order to act accordingly. Either way works, practicality and coding style decides which to go with. To actually make this command available (only to Developers, due to the lock on it), we add it to the default Account command set. Open the file mygame/commands/default_cmdsets.py and find the AccountCmdSet class:

# mygame/commands/default_cmdsets.py

# [...] from commands.command import CmdMakeGM

class AccountCmdSet(default_cmds.AccountCmdSet): # [...] def at_cmdset_creation(self): # [...] self.add(CmdMakeGM())

Finally, issue the @reload command to update the server to your changes. Developer-level players (or the superuser) should now have the @gm/@ungm command available.

13.12.3 Character sheet

Short version

• Use Evennia’s EvTable/EvForm to build a Character sheet • Tie individual sheets to a given Character. • Add new commands to modify the Character sheet, both by Accounts and GMs. • Make the Character sheet lockable by a GM, so the Player can no longer modify it.

Building a Character sheet

There are many ways to build a Character sheet in text, from manually pasting strings together to more automated ways. Exactly what is the best/easiest way depends on the sheet one tries to create. We will here show two examples using the EvTable and EvForm utilities.Later we will create 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 for how to add color to the tables and forms.

Making a sheet with EvTable

EvTable is a text-table generator. It helps with displaying text in ordered rows and columns. This is an example of using it in code:

13.12. Evennia for roleplaying sessions 329 Evennia Documentation, Release 0.9

# this can be tried out in a Python shell like iPython

from evennia.utils import evtable

# we hardcode these for now, we'll get them as input later STR, CON, DEX, INT, WIS, CHA= 12, 13,8, 10,9, 13

table= evtable.EvTable("Attr","Value", table=[ ["STR","CON","DEX","INT","WIS","CHA"], [STR, CON, DEX, INT, WIS, CHA] ], align='r', border="incols")

Above, we create a two-column table by supplying the two columns directly. We also tell the table to be right-aligned and to use the “incols” border type (borders drawns only in between columns). The EvTable class takes a lot of arguments for customizing its look, you can see some of the possible keyword arguments here. Once you have the table you could also retroactively add new columns and rows to it with table.add_row() and table. add_column(): if necessary the table will expand with empty rows/columns to always remain rectangular. The result from printing the above table will be

table_string= str(table)

print(table_string)

Attr| Value ~~~~~~+~~~~~~~ STR| 12 CON| 13 DEX|8 INT| 10 WIS|9 CHA| 13

This is a minimalistic but effective Character sheet. By combining the table_string with other strings one could build up a reasonably full graphical representation of a Character. For more advanced layouts we’ll look into EvForm next.

Making a sheet with EvForm

EvForm allows the creation of a two-dimensional “graphic” made by text characters. On this surface, one marks and tags rectangular regions (“cells”) to be filled with content. This content can be either normal strings or EvTable instances (see the previous section, one such instance would be the table variable in that example). In the case of a Character sheet, these cells would be comparable to a line or box where you could enter the name of your character or their strength score. EvMenu also easily allows to update the content of those fields in code (it use EvTables so you rebuild the table first before re-sending it to EvForm). The drawback of EvForm is that its shape is static; if you try to put more text in a region than it was sized for, the text will be cropped. Similarly, if you try to put an EvTable instance in a field too small for it, the EvTable will do its best to try to resize to fit, but will eventually resort to cropping its data or even give an error if too small to fit any data. An EvForm is defined in a Python module. Create a new file mygame/world/charsheetform.py and modify it thus:

330 Chapter 13. Tutorials Evennia Documentation, Release 0.9

#coding=utf-8

# in mygame/world/charsheetform.py

FORMCHAR="x" TABLECHAR="c"

FORM= """ .------. | | | Name: xxxxxxxxxxxxxx1xxxxxxxxxxxxxxx | | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | | | >------< | | | ccccccccccc Advantages: | | ccccccccccc xxxxxxxxxxxxxxxxxxxxxx | | ccccccccccc xxxxxxxxxx3xxxxxxxxxxx | | ccccccccccc xxxxxxxxxxxxxxxxxxxxxx | | ccccc2ccccc Disadvantages: | | ccccccccccc xxxxxxxxxxxxxxxxxxxxxx | | ccccccccccc xxxxxxxxxx4xxxxxxxxxxx | | ccccccccccc xxxxxxxxxxxxxxxxxxxxxx | | | +------+ """

The #coding statement (which must be put on the very first line to work) tells Python to use the utf-8 encoding for the file. Using the FORMCHAR and TABLECHAR we define what single-character we want to use to “mark” the regions of the character sheet holding cells and tables respectively. Within each block (which must be separated from one another by at least one non-marking character) we embed identifiers 1-4 to identify each block. The identifier could be any single character except for the FORMCHAR and TABLECHAR You can still use FORMCHAR and TABLECHAR elsewhere in your sheet, but not in a way that it would identify a cell/table. The smallest identifiable cell/table area is 3 characters wide including the identifier (for example x2x). Now we will map content to this form.

# again, this can be tested in a Python shell

# hard-code this info here, later we'll ask the # account for this info. We will re-use the 'table' # variable from the EvTable example.

NAME="John, the wise old admin with a chip on his shoulder" ADVANTAGES="Language-wiz, Intimidation, Firebreathing" DISADVANTAGES="Bad body odor, Poor eyesight, Troubled history" from evennia.utils import evform

# load the form from the module form= evform.EvForm("world/charsheetform.py")

# map the data to the form form.map(cells={"1":NAME,"3": ADVANTAGES,"4": DISADVANTAGES}, tables={"2":table})

13.12. Evennia for roleplaying sessions 331 Evennia Documentation, Release 0.9

We create some RP-sounding input and re-use the table variable from the previous EvTable example. Note, that if you didn’t want to create the form in a separate module you could also load it directly into the EvForm call like this: EvForm(form={"FORMCHAR":"x", "TABLECHAR":"c", "FORM": formstring}) where FORM specifies the form as a string in the same way as listed in the module above. Note however that the very first line of the FORM string is ignored, so start with a \n. We then map those to the cells of the form: print(form)

.------. || | Name: John, the wise old admin with | | a chip on his shoulder| || >------< || | Attr|Value Advantages:| |~~~~~+~~~~~ Language-wiz,| | STR| 12 Intimidation,| | CON| 13 Firebreathing| | DEX|8 Disadvantages:| | INT| 10 Bad body odor, Poor| | WIS|9 eyesight, Troubled| | CHA| 13 history| || +------+

As seen, the texts and tables have been slotted into the text areas and line breaks have been added where needed. We chose to just enter the Advantages/Disadvantages as plain strings here, meaning long names ended up split between rows. If we wanted more control over the display we could have inserted \n line breaks after each line or used a borderless EvTable to display those as well.

Tie a Character sheet to a Character

We will assume we go with the EvForm example above. We now need to attach this to a Character so it can be modified. For this we will modify our Character class a little more:

# mygame/typeclasses/character.py from evennia.utils import evform, evtable

[...] class Character(DefaultCharacter): [...] def at_object_creation(self): "called only once, when object is first created" # we will use this to stop account from changing sheet self.db.sheet_locked= False # we store these so we can build these on demand self.db.chardata={"str":0, "con":0, "dex":0, "int":0, "wis":0, (continues on next page)

332 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) "cha":0, "advantages":"", "disadvantages":""} self.db.charsheet= evform.EvForm("world/charsheetform.py") self.update_charsheet()

def update_charsheet(self): """ Call this to update the sheet after any of the ingoing data has changed. """ data= self.db.chardata table= evtable.EvTable("Attr","Value", table=[ ["STR","CON","DEX","INT","WIS","CHA"], [data["str"], data["con"], data["dex"], data["int"], data["wis"], data["cha"]]], align='r', border="incols") self.db.charsheet.map(tables={"2": table}, cells={"1":self.key, "3":data["advantages"], "4":data["disadvantages"]})

Use @reload to make this change available to all newly created Characters. Already existing Characters will not have the charsheet defined, since at_object_creation is only called once. The easiest to force an existing Character to re-fire its at_object_creation is to use the @typeclass command in-game:

@typeclass/force

Command for Account to change Character sheet

We will add a command to edit the sections of our Character sheet. Open mygame/commands/command.py.

# at the end of mygame/commands/command.py

ALLOWED_ATTRS=("str","con","dex","int","wis","cha") ALLOWED_FIELDNAMES= ALLOWED_ATTRS+\ ("name","advantages","disadvantages") def _validate_fieldname(caller, fieldname): "Helper function to validate field names." if fieldname not in ALLOWED_FIELDNAMES: err="Allowed field names: %s"%(",".join(ALLOWED_FIELDNAMES)) caller.msg(err) return False if fieldname in ALLOWED_ATTRS and not value.isdigit(): caller.msg("%s must receive a number."% fieldname) return False return True class CmdSheet(MuxCommand): """ Edit a field on the character sheet

Usage: (continues on next page)

13.12. Evennia for roleplaying sessions 333 Evennia Documentation, Release 0.9

(continued from previous page) @sheet field value

Examples: @sheet name Ulrik the Warrior @sheet dex 12 @sheet advantages Super strength, Night vision

If given without arguments, will view the current character sheet.

Allowed field names are: name, str, con, dex, int, wis, cha, advantages, disadvantages

"""

key="sheet" aliases="editsheet" locks="cmd: perm(Players)" help_category="RP"

def func(self): caller= self.caller if not self.args or len(self.args)<2: # not enough arguments. Display the sheet if sheet: caller.msg(caller.db.charsheet) else: caller.msg("You have no character sheet.") return

# if caller.db.sheet_locked: caller.msg("Your character sheet is locked.") return

# split input by whitespace, once fieldname, value= self.args.split( None,1) fieldname= fieldname.lower() # ignore case

if not _validate_fieldnames(caller, fieldname): return if fieldname =="name": self.key= value else: caller.chardata[fieldname]= value caller.update_charsheet() caller.msg("%s was set to %s."% (fieldname, value))

Most of this command is error-checking to make sure the right type of data was input. Note how the sheet_locked Attribute is checked and will return if not set. This command you import into mygame/commands/default_cmdsets.py and add to the CharacterCmdSet, in the same way the @gm command was added to the AccountCmdSet earlier.

334 Chapter 13. Tutorials Evennia Documentation, Release 0.9

Commands for GM to change Character sheet

Game masters use basically the same input as Players do to edit a character sheet, except they can do it on other players than themselves. They are also not stopped by any sheet_locked flags.

# continuing in mygame/commands/command.py class CmdGMsheet(MuxCommand): """ GM-modification of char sheets

Usage: @gmsheet character [= fieldname value]

Switches: lock - lock the character sheet so the account can no longer edit it (GM's still can) unlock - unlock character sheet for Account editing.

Examples: @gmsheet Tom @gmsheet Anna = str 12 @gmsheet/lock Tom

""" key="gmsheet" locks="cmd: perm(Admins)" help_category="RP"

def func(self): caller= self.caller if not self.args: caller.msg("Usage: @gmsheet character [= fieldname value]")

if self.rhs: # rhs (right-hand-side) is set only if a '=' # was given. if len(self.rhs)<2: caller.msg("You must specify both a fieldname and value.") return fieldname, value= self.rhs.split( None,1) fieldname= fieldname.lower() if not _validate_fieldname(caller, fieldname): return charname= self.lhs else: # no '=', so we must be aiming to look at a charsheet fieldname, value= None, None charname= self.args.strip()

character= caller.search(charname, global_search= True) if not character: return

if "lock" in self.switches: if character.db.sheet_locked: caller.msg("The character sheet is already locked.") (continues on next page)

13.12. Evennia for roleplaying sessions 335 Evennia Documentation, Release 0.9

(continued from previous page) else: character.db.sheet_locked= True caller.msg("%s can no longer edit their character sheet."% character.

˓→key) elif "unlock" in self.switches: if not character.db.sheet_locked: caller.msg("The character sheet is already unlocked.") else: character.db.sheet_locked= False caller.msg("%s can now edit their character sheet."% character.key)

if fieldname: if fieldname =="name": character.key= value else: character.db.chardata[fieldname]= value character.update_charsheet() caller.msg("You set %s's %s to %s."% (character.key, fieldname, value) else: # just display caller.msg(character.db.charsheet)

The @gmsheet command takes an additional argument to specify which Character’s character sheet to edit. It also takes /lock and /unlock switches to block the Player from tweaking their sheet. Before this can be used, it should be added to the default CharacterCmdSet in the same way as the normal @sheet. Due to the lock set on it, this command will only be available to Admins (i.e. GMs) or higher permission levels.

13.12.4 Dice roller

Evennia’s contrib folder already comes with a full dice roller. To add it to the game, simply import contrib.dice. CmdDice into mygame/commands/default_cmdsets.py and add CmdDice to the CharacterCmdset as done with other commands in this tutorial. After a @reload you will be able to roll dice using normal RPG-style format:

roll2d6+3 7

Use help dice to see what syntax is supported or look at evennia/contrib/dice.py to see how it’s imple- mented.

13.12.5 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. Here are some useful highlights: • @dig roomname;alias = exit_there;alias, exit_back;alias - this is the basic command for digging a new room. You can specify any exit-names and just enter the name of that exit to go there. • @tunnel direction = roomname - this is a specialized command that only accepts directions in the cardinal directions (n,ne,e,se,s,sw,w,nw) as well as in/out and up/down. It also automatically builds “matching” exits back in the opposite direction. • @create/drop objectname - this creates and drops a new simple object in the current location.

336 Chapter 13. Tutorials Evennia Documentation, Release 0.9

• @desc obj - change the look-description of the object. • @tel object = location - teleport an object to a named location. • @search objectname - locate an object in the database. TODO: Describe how to add a logging room, that logs says and poses to a log file that people can access after the fact.

13.12.6 Channels

Evennia comes with Channels 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. • addcom channel - join an existing channel. Use addcom alias = channel to add a new alias you can use to talk to the channel, as many as desired. • delcom alias or channel - remove an alias from a channel or, if the real channel name is given, unsub- scribe completely. • @channels lists all available channels, including your subscriptions and any aliases you have set up for them. You can read channel history: if you for example are chatting on the public channel you can do public/history to see the 20 last posts to that channel or public/history 32 to view twenty posts backwards, starting with the 32nd from the end.

13.12.7 PMs

To send PMs to one another, players can use the @page (or tell) command:

page recipient= message page recipient, recipient,...= message

Players can use page alone to see the latest messages. This also works if they were not online when the message was sent.

13.13 Zones

Say you create a room named Meadow in your nice big forest MUD. That’s all nice and dandy, but what if you, in the other end of that forest want another Meadow? As a game creator, this can cause all sorts of confusion. For example, teleporting to Meadow will now give you a warning that there are two Meadow s and you have to select which one. It’s no problem to do that, you just choose for example to go to 2-meadow, but unless you examine them you couldn’t be sure which of the two sat in the magical part of the forest and which didn’t. Another issue is if you want to group rooms in geographic regions. Let’s say the “normal” part of the forest should have separate weather patterns from the magical part. Or maybe a magical disturbance echoes through all magical- forest rooms. It would then be convenient to be able to simply find all rooms that are “magical” so you could send messages to them.

13.13. Zones 337 Evennia Documentation, Release 0.9

13.13.1 Zones in Evennia

Zones try to separate rooms by global location. In our example we would separate the forest into two parts - the magical and the non-magical part. Each have a Meadow and rooms belonging to each part should be easy to retrieve. Many MUD codebases hardcode zones as part of the engine and database. Evennia does no such distinction due to the fact that rooms themselves are meant to be customized to any level anyway. Below is a suggestion for how to implement zones in Evennia. All objects in Evennia can hold any number of Tags. Tags are short labels that you attach to objects. They make it very easy to retrieve groups of objects. An object can have any number of different tags. So let’s attach the relevant tag to our forest: forestobj.tags.add("magicalforest", category="zone")

You could add this manually, or automatically during creation somehow (you’d need to modify your @dig command for this, most likely). You can also use the default @tag command during building:

@tag forestobj= magicalforest : zone

Henceforth you can then easily retrieve only objects with a given tag: import evennia rooms= evennia.search_tag("magicalforest", category="zone")

13.13.2 Using typeclasses and inheritance for zoning

The tagging or aliasing systems above don’t instill any sort of functional difference between a magical forest room and a normal one - they are just arbitrary ways to mark objects for quick retrieval later. Any functional differences must be expressed using Typeclasses. Of course, an alternative way to implement zones themselves is to have all rooms/objects in a zone inherit from a given typeclass parent - and then limit your searches to objects inheriting from that given parent. The effect would be similar but you’d need to expand the search functionality to properly search the inheritance tree.

13.14 Command Duration

Before reading this tutorial, if you haven’t done so already, you might want to read the documentation on commands to get a basic understanding of how commands work in Evennia.

In some types of games a command should not start and finish immediately. Loading a crossbow might take a bit of time to do - time you don’t have when the enemy comes rushing at you. Crafting that armour will not be immediate either. For some types of games the very act of moving or changing pose all comes with a certain time associated with it.

13.14.1 The simple way to pause commands with yield

Evennia allows a shortcut in syntax to create simple pauses in commands. This

338 Chapter 13. Tutorials Evennia Documentation, Release 0.9

syntax uses the yield keyword. The yield keyword is used in Python to create generators, although you don’t need to know what generators are to use this syntax. A short example will probably make it clear:

class CmdTest(Command):

""" A test command just to test waiting.

Usage: test

"""

key = "test" locks = "cmd:all()"

def func(self): self.msg("Before ten seconds...") yield 10 self.msg("Afterwards.")

| 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`_.

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 run it, you’ll see the first message, then, after a pause of ten seconds, the next message. You can use yield several times in your command.

This syntax will not “freeze” all commands. While the command is “pausing”, you can execute other commands (or even call the same command again). And other players aren’t frozen either.

Note: this will not save anything in the database. If you reload the game while a command is “paused”, it will not resume after the server has reloaded.

13.14.2 The more advanced way with utils.delay

The yield syntax is easy to read, easy to understand, easy to use. But it’s not that flexible if you want more advanced options. Learning to use alternatives might be much worth it in the end. Below is a simple command example for adding a duration for a command to finish.

13.14. Command Duration 339 Evennia Documentation, Release 0.9

from evennia import default_cmds, utils

class CmdEcho(default_cmds.MuxCommand): """ wait for an echo

Usage: echo

Calls and waits for an echo """ key="echo" locks="cmd:all()"

def func(self): """ This is called at the initial shout. """ self.caller.msg("You shout' %s' and wait for an echo ..."% self.args) # this waits non-blocking for 10 seconds, then calls self.echo utils.delay(10, self.echo) # call echo after 10 seconds

def echo(self): "Called after 10 seconds." shout= self.args string="You hear an echo: %s ... %s ... %s" string= string% (shout.upper(), shout.capitalize(), shout.lower()) self.caller.msg(string)

Import this new echo command into the default command set and reload the server. You will find that it will take 10 seconds before you see your shout coming back. You will also find that this is a non-blocking effect; you can issue other commands in the interim and the game will go on as usual. The echo will come back to you in its own time.

About utils.delay()

utils.delay(timedelay, callback, persistent=False, *args, **kwargs) is a useful func- tion. It will wait timedelay seconds, then call the callback function, optionally passing to it the arguments provided to utils.delay by way of *args and/or **kwargs‘. Note: The callback argument should be provided with a python path to the desired function, for instance my_object.my_function instead of my_object.my_function(). Otherwise my_function would get called and run immediately upon attempting to pass it to the delay function. If you want to provide arguments for utils.delay to use, when calling your callback function, you have to do it separatly, for instance using the utils.delay *args and/or **kwargs, as mentioned above. If you are not familiar with the syntax *args and **kwargs, see the Python documentation here. Looking at it you might think that utils.delay(10, callback) in the code above is just an alternative to some more familiar thing like time.sleep(10). This is not the case. If you do time.sleep(10) you will in fact freeze the entire server for ten seconds! The utils.delay()is a thin wrapper around a Twisted Deferred that will delay execution until 10 seconds have passed, but will do so asynchronously, without bothering anyone else (not even you - you can continue to do stuff normally while it waits to continue). The point to remember here is that the delay() call will not “pause” at that point when it is called (the way yield does in the previous section). The lines after the delay() call will actually execute right away. What you must do is to tell it which function to call after the time has passed (its “callback”). This may sound strange at first, but it is normal practice in asynchronous systems. You can also link such calls together as seen below:

340 Chapter 13. Tutorials Evennia Documentation, Release 0.9

from evennia import default_cmds, utils

class CmdEcho(default_cmds.MuxCommand): """ waits for an echo

Usage: echo

Calls and waits for an echo """ key="echo" locks="cmd:all()"

def func(self): "This sets off a chain of delayed calls" self.caller.msg("You shout' %s', waiting for an echo ..."% self.args)

# wait 2 seconds before calling self.echo1 utils.delay(2, self.echo1)

# callback chain, started above def echo1(self): "First echo" self.caller.msg("... %s"% self.args.upper()) # wait 2 seconds for the next one utils.delay(2, self.echo2)

def echo2(self): "Second echo" self.caller.msg("... %s"% self.args.capitalize()) # wait another 2 seconds utils.delay(2, callback=self.echo3)

def echo3(self): "Last echo" self.caller.msg("... %s ..."% self.args.lower())

The above version will have the echoes arrive one after another, each separated by a two second delay.

> echo Hello! ... HELLO! ... Hello! ... hello! ...

13.14.3 Blocking commands

As mentioned, a great thing about the delay introduced by yield or utils.delay() is that it does not block. It just goes on in the background and you are free to play normally in the interim. In some cases this is not what you want however. Some commands should simply “block” other commands while they are running. If you are in the process of crafting a helmet you shouldn’t be able to also start crafting a shield at the same time, or if you just did a huge power-swing with your weapon you 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 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

13.14. Command Duration 341 Evennia Documentation, Release 0.9

is a very efficient, reliable and passive solution. The drawback is that there is nothing to tell the Player when enough time has passed unless they keep trying. Here is an example where we will use utils.delay to tell the player when the cooldown has passed:

from evennia import utils, default_cmds

class CmdBigSwing(default_cmds.MuxCommand): """ swing your weapon in a big way

Usage: swing

Makes a mighty swing. Doing so will make you vulnerable to counter-attacks before you can recover. """ key="bigswing" locks="cmd:all()"

def func(self): "Makes the swing"

if self.caller.ndb.off_balance: # we are still off-balance. self.caller.msg("You are off balance and need time to recover!") return

# [attack/hit code goes here ...] self.caller.msg("You swing big! You are off balance now.")

# set the off-balance flag self.caller.ndb.off_balance= True

# wait 8 seconds before we can recover. During this time # we won't be able to swing again due to the check at the top. utils.delay(8, self.recover)

def recover(self): "This will be called after 8 secs" del self.caller.ndb.off_balance self.caller.msg("You regain your balance.")

Note how, after the cooldown, the user will get a message telling them they are now ready for another swing. By storing the off_balance flag on the character (rather than on, say, the Command instance itself) it can be accessed by other Commands too. Other attacks may also not work when you are off balance. You could also have an enemy Command check your off_balance status to gain bonuses, to take another example.

13.14.4 Abortable commands

One can imagine that you will want to abort a long-running command before it has a time to finish. If you are in the middle of crafting your armor you will probably want to stop doing that when a monster enters your smithy. You can implement this in the same way as you do the “blocking” command above, just in reverse. Below is an example of a crafting command that can be aborted by starting a fight:

342 Chapter 13. Tutorials Evennia Documentation, Release 0.9

from evennia import utils, default_cmds class CmdCraftArmour(default_cmds.MuxCommand): """ Craft armour

Usage: craft

This will craft a suit of armour, assuming you have all the components and tools. Doing some other action (such as attacking someone) will abort the crafting process. """ key="craft" locks="cmd:all()"

def func(self): "starts crafting"

if self.caller.ndb.is_crafting: self.caller.msg("You are already crafting!") return if self._is_fighting(): self.caller.msg("You can't start to craft" "in the middle of a fight!") return

# [Crafting code, checking of components, skills etc]

# Start crafting self.caller.ndb.is_crafting= True self.caller.msg("You start crafting ...") utils.delay(60, self.step1)

def _is_fighting(self): "checks if we are in a fight." if self.caller.ndb.is_fighting: del self.caller.ndb.is_crafting return True

def step1(self): "first step of armour construction" if self._is_fighting(): return self.msg("You create the first part of the armour.") utils.delay(60, callback=self.step2)

def step2(self): "second step of armour construction" if self._is_fighting(): return self.msg("You create the second part of the armour.") utils.delay(60, step3)

def step3(self): "last step of armour construction"

(continues on next page)

13.14. Command Duration 343 Evennia Documentation, Release 0.9

(continued from previous page) if self._is_fighting(): return

# [code for creating the armour object etc]

del self.caller.ndb.is_crafting self.msg("You finalize your armour.")

# example of a command that aborts crafting

class CmdAttack(default_cmds.MuxCommand): """ attack someone

Usage: attack

Try to cause harm to someone. This will abort eventual crafting you may be currently doing. """ key="attack" aliases=["hit","stab"] locks="cmd:all()"

def func(self): "Implements the command"

self.caller.ndb.is_fighting= True

# [...]

The above code creates a delayed crafting command that will gradually create the armour. If the attack command is issued during this process it will set a flag that causes the crafting to be quietly canceled next time it tries to update.

13.14.5 Persistent delays

In the latter examples above we used .ndb storage. This is fast and easy but it will reset all cooldowns/blocks/crafting etc if you reload the server. If you don’t want that you can replace .ndb with .db. But even this won’t help because the yield keyword is not persisent and nor is the use of delay shown above. To resolve this you can use delay with the persistent=True keyword. But wait! Making something persistent will add some extra complications, because now you must make sure Evennia can properly store things to the database. Here is the original echo-command reworked to function with persistence:

from evennia import default_cmds, utils

# this is now in the outermost scope and takes two args! def echo(caller, args): "Called after 10 seconds." shout= args string="You hear an echo: %s ... %s ... %s" string= string% (shout.upper(), shout.capitalize(), shout.lower()) caller.msg(string)

(continues on next page)

344 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) class CmdEcho(default_cmds.MuxCommand): """ wait for an echo

Usage: echo

Calls and waits for an echo """ key="echo" locks="cmd:all()"

def func(self): """ This is called at the initial shout. """ self.caller.msg("You shout' %s' and wait for an echo ..."% self.args) # this waits non-blocking for 10 seconds, then calls echo(self.caller, self.

˓→args) utils.delay(10, echo, self.caller, self.args, persistent=True) # changes!

Above you notice two changes: • The callback (echo) was moved out of the class and became its own stand-alone function in the outermost scope of the module. It also now takes caller and args as arguments (it doesn’t have access to them directly since this is now a stand-alone function). • utils.delay specifies the echo function (not self.echo - it’s no longer a method!) and sends self. caller and self.args as arguments for it to use. We also set persistent=True. The reason for this change is because Evennia needs to pickle the callback into storage and it cannot do this correctly when the method sits on the command class. Now this behave the same as the first version except if you reload (or even shut down) the server mid-delay it will still fire the callback when the server comes back up (it will resume the countdown and ignore the downtime).

13.15 Command Cooldown

Some types of games want to limit how often a command can be run. If a character casts the spell Firestorm, you might not want them to spam that command over and over. Or in an advanced combat system, a massive swing may offer a chance of lots of damage at the cost of not being able to re-do it for 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‘_, the two might be useful to combine if you want to echo some message to the user after the cooldown ends.

13.15.1 Non-persistent cooldown

This little recipe will limit how often a particular command can be run. Since Commands are class instances, and those are cached in memory, a command

13.15. Command Cooldown 345 Evennia Documentation, Release 0.9 instance will remember things you store on it. So just store the current time of execution! Next time the command is run, it just needs to check if it has that time stored, and compare it with the current time to see if a desired delay has passed.

import time from evennia import default_cmds class CmdSpellFirestorm(default_cmds.MuxCommand): """ Spell - Firestorm

Usage: cast firestorm

This will unleash a storm of flame. You can only release one firestorm every five minutes (assuming you have the mana). """ key="cast firestorm" locks="cmd:isFireMage()"

def func(self): "Implement the spell"

# check cooldown (5 minute cooldown) now= time.time() if hasattr(self,"lastcast") and \ now- self.lastcast<5 * 60: message="You cannot cast this spell again yet." self.caller.msg(message) return

#[the spell effect is implemented]

# if the spell was successfully cast, store the casting time self.lastcast= now

We just check the lastcast flag, and update it if everything works out. Simple and very effective since everything is just stored in memory. The drawback of this simple scheme is that it’s non-persistent. If you do @reload, the cache is cleaned and all such ongoing cooldowns will be forgotten. It is also limited only to this one command, other commands cannot (easily) check for this value.

13.15.2 Persistent cooldown

This is essentially the same mechanism as the simple one above, except we use the database to store the information which means the cooldown will survive a server reload/reboot. Since commands themselves have no representation in the database, you need to use the caster for the storage.

346 Chapter 13. Tutorials Evennia Documentation, Release 0.9

# inside the func() of CmdSpellFirestorm as above

# check cooldown (5 minute cooldown) now= time.time() lastcast= self.caller.db.firestorm_lastcast if lastcast and now- lastcast<5 * 60: message="You need to wait before casting this spell again." self.caller.msg(message) return

#[the spell effect is implemented]

# if the spell was successfully cast, store the casting time self.caller.db.firestorm_lastcast= now

Since we are storing as an ‘Attribute‘_, 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 for example let all fire-related spells check the same cooldown to make sure the casting of Firestorm blocks all fire-related spells for a while. Or, in the case of taking that big swing with the sword, this could now block all other types of attacks for a while before the warrior can recover. tutorial: Command-Duration#Blocking-Commands .. _Attribute: Attributes.html

13.16 Mass and weight for objects

An easy addition to add dynamic variety to your world objects is to give them some mass. Why mass and not weight? Weight varies in setting; for example things on the Moon weigh 1/6 as much. On Earth’s surface and in most environments, no relative weight factor is needed.

In most settings, mass can be used as weight to spring a pressure plate trap or a floor giving way, determine a character’s burden weight for travel speed. . . The total mass of an object can contribute to the force of a weapon swing, or a speeding meteor to give it a potential striking force.

13.16.1 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 its contents.

13.16. Mass and weight for objects 347 Evennia Documentation, Release 0.9

# inside your mygame/typeclasses/objects.py class Object(DefaultObject): # [...] def get_mass(self): mass= self.attributes.get('mass',1) # Default objects have 1 unit mass. return mass+ sum(obj.get_mass() for obj in self.contents)

Adding the get_mass definition to the objects you want to sum up the masses for is done with Python’s “sum” function which operates on all the contents, in this case by summing them to return a total mass value.

If you only wanted specific object types to have mass or have the new object type in a different module, see [[Adding-Object-Typeclass-Tutorial]] with its Heavy class object. You could set the default for Heavy types to something much larger than 1 gram or whatever unit you want to use. Any non-default mass would be stored on the mass [[Attributes]] of the objects.

13.16.2 Characters and rooms

You can add a get_mass definition to characters and rooms, also. If you were in a one metric-ton elevator with four other friends also wearing armor and carrying gold bricks, you might wonder if this elevator’s going to move, and how fast.

Assuming the unit is grams and the elevator itself weights 1,000 kilograms, it would already be @set elevator/mass=1000000, we’re @set me/mass=85000 and our armor is @set armor/mass=50000. We’re each carrying 20 gold bars each @set gold bar/mass=12400 then step into the elevator and see the following message in the elevator’s appearance: Elevator weight and contents should not exceed 3 metric tons. Are we safe? Maybe not if you consider dynamic loading. But at rest:

# Elevator object knows when it checks itself: if self.get_mass()< 3000000: pass # Elevator functions as normal. else: pass # Danger! Alarm sounds, cable snaps, elevator stops...

13.16.3 Inventory

Example of listing mass of items in your inventory: class CmdInventory(MuxCommand): """ view inventory Usage: inventory (continues on next page)

348 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) inv Switches: /weight to display all available channels. Shows your inventory: carrying, wielding, wearing, obscuring. """

key="inventory" aliases=["inv","i"] locks="cmd:all()"

def func(self): "check inventory" items= self.caller.contents if not items: string="You are not carrying anything." else: table= prettytable.PrettyTable(["name","desc"]) table.header= False table.border= False for item in items: second= item.get_mass() \ if 'weight' in self.switches else item.db.desc table.add_row(["%s"% item.get_display_name(self.caller.sessions), second and second or ""]) string="|wYou are carrying: \n%s"% table self.caller.msg(string)

13.17 Default Exit Errors

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 and an Exit Command 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 command; Evennia doesn’t care about the difference:

> jump out the window Command'jump out the window' is not available. Type"help" for help.

Many games don’t need this type of freedom however. They define only the cardinal directions as valid exit names (Evennia’s @tunnel command also offers this functionality). In this case, the error starts to look less logical:

> west Command 'west' is not available. Maybe you meant "@set" or "@reset"?

Since we for our particular game know that west is an exit direction, it would be better if the error message just told us that we couldn’t go there.

13.17.1 Adding default error commands

To solve this you need to be aware of how to write and add new commands. 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,

13.17. Default Exit Errors 349 Evennia Documentation, Release 0.9 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 of commands:

# for example in a file mygame/commands/movecommands.py from evennia import default_cmds class CmdExitError(default_cmds.MuxCommand): "Parent class for all exit-errors." locks="cmd:all()" arg_regex=r"\s|$" auto_help= False def func(self): "returns the error" self.caller.msg("You cannot move %s."% self.key) class CmdExitErrorNorth(CmdExitError): key="north" aliases=["n"] class CmdExitErrorEast(CmdExitError): key="east" aliases=["e"] class CmdExitErrorSouth(CmdExitError): key="south" aliases=["s"] class CmdExitErrorWest(CmdExitError): key="west" aliases=["w"]

Make sure to add the directional commands (not their parent) to the CharacterCmdSet class in mygame/ commands/default_cmdsets.py:

# in mygame/commands/default_cmdsets.py from commands import movecommands

# [...] class CharacterCmdSet(default_cmds.CharacterCmdSet): # [...] def at_cmdset_creation(self): # [...] self.add(movecommands.CmdExitErrorNorth()) self.add(movecommands.CmdExitErrorEast()) self.add(movecommands.CmdExitErrorSouth()) self.add(movecommands.CmdExitErrorWest())

After a @reload these commands (assuming you don’t get any errors - check your log) will be loaded. What happens henceforth is that if you are in a room with an Exitobject (let’s say it’s “north”), the proper Exit-command will overload your error command (also named “north”). But if you enter an direction without having a matching exit for it, you will fallback to your default error commands:

> east You cannot move east.

Further expansions by the exit system (including manipulating the way the Exit command itself is created) can be done

350 Chapter 13. Tutorials Evennia Documentation, Release 0.9 by modifying the Exit typeclass directly.

13.17.2 Additional Comments

So why didn’t we create a single error command above? Something like this: class CmdExitError(default_cmds.MuxCommand): "Handles all exit-errors." key="error_cmd" aliases=["north","n", "east","e", "south","s", "west","w"] #[...]

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 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. So the above example would work fine as long as there were no Exits at all in the room. But what happens when we enter a room with an exit “north”? The Exit’s cmdset is merged onto the default one, and since there is an alias match, the system determines our CmdExitError to be identical. It is thus overloaded by the Exit command (which also correctly defaults to a higher priority). The result is that you can go through the north exit normally but none of the error messages for the other directions are available since the single error command was completely overloaded by the single matching “north” exit-command.

13.18 Gametime Tutorial

A lot of games use a separate time system we refer to as game time. This runs in parallel to what we usually think of as real time. The game time might run at a different speed, use different names for its time units or might even use a completely custom calendar. You don’t need to rely on a game time system at all. But if you do, Evennia offers basic tools to handle these various situations. This tutorial will walk you through these features.

13.18.1 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 historical or futuristic settings. Using a standard calendar has some advantages: • Handling repetitive actions is much easier, since converting from the real time experience to the in-game per- ceived one is easy. • The intricacies of the real world calendar, with leap years and months of different length etc are automatically handled by the system. Evennia’s game time features assume a standard calendar (see the relevant section below for a custom calendar).

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:

13.18. Gametime Tutorial 351 Evennia Documentation, Release 0.9

# The time factor dictates if the game world runs faster (timefactor>1) # or slower (timefactor<1) than the real world. TIME_FACTOR= 2.0

# The starting point of your game time (the epoch), in seconds. # In Python a value of 0 means Jan 1 1970 (use negatives for earlier # start date). This will affect the returns from the utils.gametime # module. TIME_GAME_EPOCH= None

By default, the game time runs twice as fast as the real time. You can set the time factor to be 1 (the game time would run exactly at the same speed than the real time) or lower (the game time will be slower than the real time). Most games choose to have the game time spinning faster (you will find some games that have a time factor of 60, meaning the game time runs sixty times as fast as the real time, a minute in real time would be an hour in game time). The epoch is a slightly more complex setting. It should contain a number of seconds that would indicate the time your game started. As indicated, an epoch of 0 would mean January 1st, 1970. If you want to set your time in the future, you just need to find the starting point in seconds. There are several ways to do this in Python, this method will show you how to do it in local time:

# We're looking for the number of seconds representing # January 1st, 2020 from datetime import datetime import time start= datetime(2020,1,1) time.mktime(start.timetuple())

This should return a huge number - the number of seconds since Jan 1 1970. Copy that directly into your settings (editing server/conf/settings.py):

TIME_GAME_EPOCH= 1577865600

Reload the game with @reload, and then use the @time command. You should see something like this:

+------+------+ | Server time|| +~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ | Current uptime| 20 seconds| | Total runtime|1 day,1 hour, 55 minutes| | First start| 2017-02-12 15:47:50.565000| | Current time| 2017-02-13 17:43:10.760000| +------+------+ | In-Game time| Real time x2| +~~~~~~~~~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ | Epoch (from settings)| 2020-01-01 00:00:00| | Total time passed:|1 day, 17 hours, 34 minutes| | Current time| 2020-01-02 17:34:55.430000| +------+------+

The line that is most relevant here is the game time epoch. You see it shown at 2020-01-01. From 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.

352 Chapter 13. Tutorials Evennia Documentation, Release 0.9

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 with some additional features to make sure the script is always executed when the game time matches the given parameters. The schedule function takes the following arguments: • The callback, a function to be called when time is up. • The keyword repeat (False by default) to indicate whether this function should be called repeatedly. • Additional keyword arguments sec, min, hour, day, month and year to describe the time to schedule. If the parameter isn’t given, it assumes the current time value of this specific unit. Here is a short example for making the sun rise every day:

# in a file ingame_time.py in mygame/world/

from evennia.utils import gametime from typeclasses.rooms import Room

def at_sunrise(): """When the sun rises, display a message in every room.""" # Browse all rooms for room in Room.objects.all(): room.msg_contents("The sun rises from the eastern horizon.")

def start_sunrise_event(): """Schedule an sunrise event to happen every day at 6 AM.""" script= gametime.schedule(at_sunrise, repeat= True, hour=6, min=0, sec=0) script.key="at sunrise"

If you want to test this function, you can easily do something like:

@py from world import ingame_time; ingame_time.start_sunrise_event()

The script will be created silently. The at_sunrise function will now be called every in-game day at 6 AM. You can use the @scripts command to see it. You could stop it using @scripts/stop. If we hadn’t set repeat the sun would only have risen once and then never again. We used the @py command here: nothing prevents you from adding the system into your game code. Remember to be careful not to add each event at startup, however, otherwise there will be a lot of overlapping events scheduled when the sun rises. The schedule function when using repeat set to True works with the higher, non-specified unit. In our example, we have specified hour, minute and second. The higher unit we haven’t specified is day: schedule assumes we mean “run the callback every day at the specified time”. Therefore, you can have an event that runs every hour at HH:30, or every month on the 3rd day. A word of caution for repeated scripts on a monthly or yearly basis: due to the variations in the real-life calendar you need to be careful when scheduling events for the end of the month or year. For example, if you set a script to run every month on the 31st it will run in January but find no such day in February, April etc. Similarly, leap years may change the number of days in the year.

13.18. Gametime Tutorial 353 Evennia Documentation, Release 0.9

13.18.2 A game time with a custom calendar

Using a custom calendar to handle game time is sometimes needed if you want to place your game in a fictional universe. For instance you may want to create the Shire calendar which Tolkien described having 12 months, each which 30 days. That would give only 360 days per year (presumably hobbits weren’t really fond of the hassle of following the astronomical calendar). Another example would be creating a planet in a different solar system with, say, days 29 hours long and months of only 18 days. Evennia handles custom calendars through an optional contrib module, called custom_gametime. Contrary to the normal gametime module described above it is not active by default.

Setting up the custom calendar

In our first example of the Shire calendar, used by hobbits in books by Tolkien, we don’t really need the notion of weeks. . . but we need the notion of months having 30 days, not 28. The custom calendar is defined by adding the TIME_UNITS setting to your settings file. It’s a dictionary containing as keys the name of the units, and as value the number of seconds (the smallest unit for us) in this unit. Its keys must be picked among the following: “sec”, “min”, “hour”, “day”, “week”, “month” and “year” but you don’t have to include them all. Here is the configuration for the Shire calendar:

TIME_UNITS={"sec":1, "min": 60, "hour": 60 * 60, "day": 60 * 60 * 24, "month": 60 * 60 * 24 * 30, "year": 60 * 60 * 24 * 30 * 12}

We give each unit we want as keys. Values represent the number of seconds in that unit. Hour is set to 60 * 60 (that is, 3600 seconds per hour). Notice that we don’t specify the week unit in this configuration: instead, we skip from days to months directly. 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:

# Time factor TIME_FACTOR=4

# Game time epoch TIME_GAME_EPOCH=0

# Units TIME_UNITS={ "sec":1, "min": 60, "hour": 60 * 60, "day": 60 * 60 * 24, "month": 60 * 60 * 24 * 30, "year": 60 * 60 * 24 * 30 * 12, }

Notice we have set a time epoch of 0. Using a custom calendar, we will come up with a nice display of time on our own. In our case the game time starts at year 0, month 0, day 0, and at midnight. Note that while we use “month”, “week” etc in the settings, your game may not use those terms in-game, instead referring to them as “cycles”, “moons”, “sand falls” etc. This is just a matter of you displaying them differently. See

354 Chapter 13. Tutorials Evennia Documentation, Release 0.9 next section.

A command to display the current game time

As pointed out earlier, the @time command is meant to be used with a standard calendar, not a custom one. We can easily create a new command though. We’ll call it time, as is often the case on other MU*. Here’s an example of how we could write it (for the example, you can create a file showtime.py in your commands directory and paste this code in it):

# in a file mygame/commands/gametime.py from evennia.contrib import custom_gametime from commands.command import Command class CmdTime(Command):

""" Display the time.

Syntax: time

"""

key="time" locks="cmd:all()"

def func(self): """Execute the time command.""" # Get the absolute game time year, month, day, hour, min, sec= custom_gametime.custom_

˓→gametime(absolute=True) string="We are in year {year}, day {day}, month {month}." string+=" \nIt's {hour:02}:{min:02}:{sec:02}." self.msg(string.format(year=year, month=month, day=day, hour=hour, min=min, sec=sec))

Don’t forget to add it in your CharacterCmdSet to see this command: from commands.gametime import CmdTime class CharacterCmdSet(default_cmds.CharacterCmdSet): """ The `CharacterCmdSet` contains general in-game commands like `look`, `get`, etc available on in-game Character objects. It is merged with the `AccountCmdSet` when an Account puppets a Character. """ key="DefaultCharacter"

def at_cmdset_creation(self): """ Populates the cmdset """ super().at_cmdset_creation() self.add(CmdTime())

13.18. Gametime Tutorial 355 Evennia Documentation, Release 0.9

Reload your game with the @reload command. You should now see the time command. If you enter it, you might see something like:

We are in year0, day0, month 0. It's 00:52:17.

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

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.

13.19 Coordinates

# Adding room coordinates in your game

This tutorial is moderately difficult in content. You might want to be familiar and at ease with some Python concepts (like properties) and possibly Django concepts (like queries), although this tutorial will try to walk you through the process and give enough explanations each time. If you don’t feel very confident with math, don’t hesitate to pause, go to the example section, which shows a tiny map, and try to walk around the code or read the explanation.

Evennia doesn’t have a coordinate system by default. Rooms and other objects are linked by location and content:

• An object can be in a location, that is, another object. Like an exit in a room. • An object can access its content. A room can see what objects uses it as location (that would include exits, rooms, characters and so on).

This system allows for a lot of flexibility and, fortunately, can be extended by other systems. Here, I offer you a way to add coordinates to every room in a way most compliant with Evennia design. This will also show you how to use coordinates, find rooms around a given point for instance.

13.19.1 Coordinates as tags

The first concept might be the most surprising at first glance: we will create coordinates as tags.

Why not attributes, wouldn’t that be easier?

356 Chapter 13. Tutorials Evennia Documentation, Release 0.9

It would. We could just do something like room.db.x = 3. The advantage of using tags is that it will be easy and effective to search. Although this might not seem like a huge advantage right now, with a database of thousands of rooms, it might make a difference, particularly if you have a lot of things based on coordinates.

Rather than giving you a step-by-step process, I’ll show you the code. Notice that we use properties to easily access and update coordinates. This is a Pythonic approach. Here’s our first Room class, that you can modify in typeclasses/rooms.py:

# in typeclasses/rooms.py from evennia import DefaultRoom class Room(DefaultRoom): """ Rooms are like any Object, except their location is None (which is default). They also use basetype_setup() to add locks so they cannot be puppeted or picked up. (to change that, use at_object_creation instead)

See examples/object.py for a list of properties and methods available on all Objects. """

@property def x(self): """Return the X coordinate or None.""" x= self.tags.get(category="coordx") return int(x) if isinstance(x, str) else None

@x.setter def x(self, x): """Change the X coordinate.""" old= self.tags.get(category="coordx") if old is not None: self.tags.remove(old, category="coordx") if x is not None: self.tags.add(str(x), category="coordx")

@property def y(self): """Return the Y coordinate or None.""" y= self.tags.get(category="coordy") return int(y) if isinstance(y, str) else None

@y.setter def y(self, y): """Change the Y coordinate.""" old= self.tags.get(category="coordy") if old is not None: self.tags.remove(old, category="coordy") if y is not None: self.tags.add(str(y), category="coordy")

(continues on next page)

13.19. Coordinates 357 Evennia Documentation, Release 0.9

(continued from previous page) @property def z(self): """Return the Z coordinate or None.""" z= self.tags.get(category="coordz") return int(z) if isinstance(z, str) else None

@z.setter def z(self, z): """Change the Z coordinate.""" old= self.tags.get(category="coordz") if old is not None: self.tags.remove(old, category="coordz") if z is not None: self.tags.add(str(z), category="coordz")

If you aren’t familiar with the concept of properties in Python, I encourage you to read a good tutorial on the subject. This article on Python properties is well-explained and should help you understand the idea.

Let’s look at our properties for x. First of all is the read property.

@property def x(self): """Return the X coordinate or None.""" x= self.tags.get(category="coordx") return int(x) if isinstance(x, str) else None

What it does is pretty simple: 1. It gets the tag of category "coordx". It’s the tag category where we store our X coordinate. The tags.get method will return None if the tag can’t be found. 2. We convert the value to an integer, if it’s a str. Remember that tags can only contain str, so we’ll need to convert it. I thought tags couldn’t contain values?

Well, technically, they can’t: they’re either here or not. But using tag categories, as we have done, we get a tag, knowing only its category. That’s the basic approach to coordinates in this tutorial.

Now, let’s look at the method that will be called when we wish to set x in our room:

@x.setter def x(self, x): """Change the X coordinate.""" old= self.tags.get(category="coordx") if old is not None: self.tags.remove(old, category="coordx") if x is not None: self.tags.add(str(x), category="coordx")

358 Chapter 13. Tutorials Evennia Documentation, Release 0.9

1. First, we remove the old X coordinate, if it exists. Otherwise, we’d end up with two tags in our room with “coordx” as their category, which wouldn’t do at all. 2. Then we add the new tag, giving it the proper category. Now what?

If you add this code and reload your game, once you’re logged in with a character in a room as its location, you can play around:

@py here.x @py here.x=0 @py here.y=3 @py here.z=-2 @py here.z= None

The code might not be that easy to read, but you have to admit it’s fairly easy to use.

13.19.2 Some additional searches

Having coordinates is useful for several reasons: 1. It can help in shaping a truly logical world, in its geography, at least. 2. It can allow to look for specific rooms at given coordinates. 3. It can be good in order to quickly find the rooms around a location. 4. It can even be great in path-finding (finding the shortest path between two rooms).

So far, our coordinate system can help with 1., but not much else. Here are some methods that we could add to the Room typeclass. These methods will just be search methods. Notice that they are class methods, since we want to get rooms.

Finding one room

First, a simple one: how to find a room at a given coordinate? Say, what is the room at X=0, Y=0, Z=0? class Room(DefaultRoom): # ... @classmethod def get_room_at(cls, x, y, z): """ Return the room at the given location or None if not found.

Args: x (int): the X coord. y (int): the Y coord. z (int): the Z coord.

Return: The room at this location (Room) or None if not found. (continues on next page)

13.19. Coordinates 359 Evennia Documentation, Release 0.9

(continued from previous page)

""" rooms= cls.objects.filter( db_tags__db_key=str(x), db_tags__db_category="coordx").filter( db_tags__db_key=str(y), db_tags__db_category="coordy").filter( db_tags__db_key=str(z), db_tags__db_category="coordz") if rooms: return rooms[0]

return None

This solution includes a bit of Django queries. Basically, what we do is reach for the object manager and search for objects with the matching tags. Again, don’t spend too much time worrying about the mechanism, the method is quite easy to use:

Room.get_room_at(5,2,-3)

Notice that this is a class method: you will call it from Room (the class), not an instance. Though you still can:

@py here.get_room_at(3,8,0)

Finding several rooms

Here’s another useful method that allows us to look for rooms around a given coordinate. This is more advanced search and doing some calculation, beware! Look at the following section if you’re lost.

from math import sqrt class Room(DefaultRoom):

# ...

@classmethod def get_rooms_around(cls, x, y, z, distance): """ Return the list of rooms around the given coordinates.

This method returns a list of tuples (distance, room) that can easily be browsed. This list is sorted by distance (the closest room to the specified position is always at the top of the list).

Args: x (int): the X coord. y (int): the Y coord. z (int): the Z coord. distance (int): the maximum distance to the specified position.

Returns: (continues on next page)

360 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) A list of tuples containing the distance to the specified position and the room at this distance. Several rooms can be at equal distance from the position.

""" # Performs a quick search to only get rooms in a square x_r= list(reversed([str(x- i) for i in range(0, distance+1)])) x_r+=[str(x+ i) for i in range(1, distance+1)] y_r= list(reversed([str(y- i) for i in range(0, distance+1)])) y_r+=[str(y+ i) for i in range(1, distance+1)] z_r= list(reversed([str(z- i) for i in range(0, distance+1)])) z_r+=[str(z+ i) for i in range(1, distance+1)] wide= cls.objects.filter( db_tags__db_key__in=x_r, db_tags__db_category="coordx").filter( db_tags__db_key__in=y_r, db_tags__db_category="coordy").filter( db_tags__db_key__in=z_r, db_tags__db_category="coordz")

# We now need to filter down this list to find out whether # these rooms are really close enough, and at what distance # In short: we change the square to a circle. rooms=[] for room in wide: x2= int(room.tags.get(category="coordx")) y2= int(room.tags.get(category="coordy")) z2= int(room.tags.get(category="coordz")) distance_to_room= sqrt( (x2- x) ** 2+ (y2- y) ** 2+ (z2- z) ** 2) if distance_to_room<= distance: rooms.append((distance_to_room, room))

# Finally sort the rooms by distance rooms.sort(key=lambda tup: tup[0]) return rooms

This gets more serious. 1. We have specified coordinates as parameters. We determine a broad range using the distance. That is, for each coordinate, we create a list of possible matches. See the example below. 2. We then search for the rooms within this broader range. It gives us a square around our location. Some rooms are definitely outside the range. Again, see the example below to follow the logic. 3. We filter down the list and sort it by distance from the specified coordinates.

Notice that we only search starting at step 2. Thus, the Django search doesn’t look and cache all objects, just a wider range than what would be really necessary. This method returns a circle of coordinates around a specified point. Django looks for a square. What wouldn’t fit in the circle is removed at step 3, which is the only part that includes systematic calculation. This method is optimized to be quick and efficient.

An example

An example might help. Consider this very simple map (a textual description follows):

13.19. Coordinates 361 Evennia Documentation, Release 0.9

4ABCD 3EFGH 2IJKL 1MNOP 1234

The X coordinates are given below. The Y coordinates are given on the left. This is a simple square with 16 rooms: 4 on each line, 4 lines of them. All the rooms are identified by letters in this example: the first line at the top has rooms A to D, the second E to H, the third I to L and the fourth M to P. The bottom-left room, X=1 and Y=1, is M. The upper-right room X=4 and Y=4 is D. So let’s say we want to find all the neighbors, distance 1, from the room . J is at X=2, Y=2. So we use:

Room.get_rooms_around(x=2, y=2, z=0, distance=1) # we'll assume a z coordinate of 0 for simplicity

1. First, this method gets all the rooms in a square around J. So it gets E F G, I J K, M N O. If you want, draw the square around these coordinates to see what’s happening. 2. Next, we browse over this list and check the real distance between J (X=2, Y=2) and the room. The four corners of the square are not in this circle. For instance, the distance between J and M is not 1. If you draw a circle of center J and radius 1, you’ll notice that the four corners of our square (E, G, M and O) are not in this circle. So we remove them. 3. We sort by distance from J. So in the end we might obtain something like this:

[ (0, J), # yes, J is part of this circle after all, with a distance of 0 (1, F), (1, I), (1, K), (1, N), ]

You can try with more examples if you want to see this in action.

To conclude

You can definitely use this system to map other objects, not just rooms. You can easily remove the ‘Z coordinate too, if you simply need X and Y.

13.20 Dialogues in events

• Next tutorial: adding a voice-operated elevator with events. This tutorial will walk you through the steps to create several dialogues with characters, using the in-game Python system. This tutorial assumes the in-game Python system is installed in your game. If it isn’t, you can follow the installation steps given in the documentation on in-game Python, and come back on this tutorial once the system is installed. You do not need to read the entire documentation, it’s a good reference, but not the easiest way to learn about it. Hence these tutorials.

362 Chapter 13. Tutorials Evennia Documentation, Release 0.9

The in-game Python system allows to run code on individual objects in some situations. You don’t have to modify the source code to add these features, past the installation. The entire system makes it easy to add specific features to some objects, but not all. This is why it can be very useful to create a dialogue system taking advantage of the in-game Python system. What will we try to do? In this tutorial, we are going to create a basic dialogue to have several characters automatically respond to specific messages said by others.

13.20.1 A first example with a first character

Let’s create a character to begin with.

@charcreate a merchant

This will create a merchant in the room where you currently are. It doesn’t have anything, like a description, you can decorate it a bit if you like. As said above, the in-game Python system consists in linking objects with arbitrary code. This code will be executed in some circumstances. Here, the circumstance is “when someone says something in the same room”, and might be more specific like “when someone says hello”. We’ll decide what code to run (we’ll actually type the code in-game). Using the vocabulary of the in-game Python system, we’ll create a callback: a callback is just a set of lines of code that will run under some conditions. You can have an overview of every “conditions” in which callbacks can be created using the @call command (short for @callback). You need to give it an object as argument. Here for instance, we could do:

@call a merchant

You should see a table with three columns, showing the list of events existing on our newly-created merchant. There are quite a lot of them, as it is, althougn no line of code has been set yet. For our system, you might be more interested by the line describing the say event:

| say|0(0)| After another character has said something in | ||| the character's room. |

We’ll create a callback on the say event, called when we say “hello” in the merchant’s room:

@call/add a merchant= say hello

Before seeing what this command displays, let’s see the command syntax itself: • @call is the command name, /add is a switch. You can read the help of the command to get the help of available switches and a brief overview of syntax. • We then enter the object’s name, here “a merchant”. You can enter the ID too (“#3” in my case), which is useful to edit the object when you’re not in the same room. You can even enter part of the name, as usual. • An equal sign, a simple separator. • The event’s name. Here, it’s “say”. The available events are displayed when you use @call without switch. • After a space, we enter the conditions in which this callback should be called. Here, the conditions represent what the other character should say. We enter “hello”. Meaning that if someone says something containing “hello” in the room, the callback we are now creating will be called. When you enter this command, you should see something like this:

13.20. Dialogues in events 363 Evennia Documentation, Release 0.9

After another character has said something in the character's room. This event is called right after another character has said something in the same location. The action cannot be prevented at this moment. Instead, this event is ideal to create keywords that would trigger a character (like a NPC) in doing something if a specific phrase is spoken in the same location.

To use this event, you have to specify a list of keywords as parameters that should be present, as separate words, in the spoken phrase. For instance, you can set a callback that would fire if the phrase spoken by the character contains"menu" or "dinner" or "lunch": @call/add...= say menu, dinner, lunch Then if one of the words is present in what the character says, this callback will fire.

Variables you can use in this event: speaker: the character speaking in this room. character: the character connected to this event. message: the text having been spoken by the character.

That’s some list of information. What’s most important to us now is: • The “say” event is called whenever someone else speaks in the room. • We can set callbacks to fire when specific keywords are present in the phrase by putting them as additional parameters. Here we have set this parameter to “hello”. We can have several keywords separated by a comma (we’ll see this in more details later). • We have three default variables we can use in this callback: speaker which contains the character who speaks, character which contains the character who’s modified by the in-game Python system (here, or merchant), and message which contains the spoken phrase. This concept of variables is important. If it makes things more simple to you, think of them as parameters in a function: they can be used inside of the function body because they have been set when the function was called. This command has opened an editor where we can type our Python code.

------Line Editor [Callback say of a merchant]------01| ------[l:01 w:000 c:0000]------(:h for help)------

For our first test, let’s type something like:

character.location.msg_contents("{character} shrugs and says:'well, yes, hello to

˓→you!'", mapping=dict(character=character))

Once you have entered this line, you can type :wq to save the editor and quit it. And now if you use the “say” command with a message containing “hello”:

You say,"Hello sir merchant!" a merchant(#3) shrugs and says: 'well, yes, hello to you!'

If you say something that doesn’t contain “hello”, our callback won’t execute. In summary: 1. When we say something in the room, using the “say” command, the “say” event of all characters (except us) is called.

364 Chapter 13. Tutorials Evennia Documentation, Release 0.9

2. The in-game Python system looks at what we have said, and checks whether one of our callbacks in the “say” event contains a keyword that we have spoken. 3. If so, call it, defining the event variables as we have seen. 4. The callback is then executed as normal Python code. Here we have called the msg_contents method on the character’s location (probably a room) to display a message to the entire room. We have also used mapping to easily display the character’s name. This is not specific to the in-game Python system. If you feel overwhelmed by the code we’ve used, just shorten it and use something more simple, for instance: speaker.msg("You have said something to me.")

13.20.2 The same callback for several keywords

It’s easy to create a callback that will be triggered if the sentence contains one of several keywords.

@call/add merchant= say trade, trader, goods

And in the editor that opens: character.location.msg_contents("{character} says:'Ho well, trade's fine as long as

˓→roads are safe.'", mapping=dict(character=character))

Then you can say something with either “trade”, “trader” or “goods” in your sentence, which should call the callback:

You say,"and how is your trade going?" a merchant(#3) says: 'Ho well, trade's fine as long as roads are safe.'

We can set several keywords when adding the callback. We just need to separate them with commas.

13.20.3 A longer callback

So far, we have only set one line in our callbacks. Which is useful, but we often need more. For an entire dialogue, you might want to do a bit more than that.

@call/add merchant= say bandit, bandits

And in the editor you can paste the following lines: character.location.msg_contents("{character} says:'Bandits he?'",

˓→mapping=dict(character=character)) character.location.msg_contents("{character} scratches his head, considering.",

˓→mapping=dict(character=character)) character.location.msg_contents("{character} whispers:'Aye, saw some of them, north

˓→from here. No trouble o' mine, but...'", mapping=dict(character=character)) speaker.msg("{character} looks at you more closely.".format(character=character.get_

˓→display_name(speaker))) speaker.msg("{character} continues in a low voice:'Ain't my place to say, but if you

˓→need to find'em, they're encamped some distance away from the road, I guess near a

˓→cave or something.'.".format(character=character.get_display_name(speaker)))

Now try to ask the merchant about bandits:

13.20. Dialogues in events 365 Evennia Documentation, Release 0.9

You say,"have you seen bandits?" a merchant(#3) says: 'Bandits he?' a merchant(#3) scratches his head, considering. a merchant(#3) whispers: 'Aye, saw some of them, north from here. No trouble o' mine,

˓→ but...' a merchant(#3) looks at you more closely. a merchant(#3) continues in a low voice: 'Ain't my place to say, but if you need to

˓→find 'em, they're encamped some distance away from the road, I guess near a cave or

˓→something.'.

Notice here that the first lines of dialogue are spoken to the entire room, but then the merchant is talking directly to the speaker, and only the speaker hears it. There’s no real limit to what you can do with this. • You can set a mood system, storing attributes in the NPC itself to tell you in what mood he is, which will influence the information he will give. . . perhaps the accuracy of it as well. • You can add random phrases spoken in some context. • You can use other actions (you’re not limited to having the merchant say something, you can ask him to move, gives you something, attack if you have a combat system, or whatever else). • The callbacks are in pure Python, so you can write conditions or loops. • You can add in “pauses” between some instructions using chained events. This tutorial won’t describe how to do that however. You already have a lot to play with.

13.20.4 Tutorial F.A.Q.

• Q: can I create several characters who would answer to specific dialogue? • A: of course. Te in-game Python system is so powerful because you can set unique code for various objects. You can have several characters answering to different things. You can even have different characters in the room answering to greetings. All callbacks will be executed one after another. • Q: can I have two characters answering to the same dialogue in exactly the same way? • A: It’s possible but not so easy to do. Usually, event grouping is set in code, and depends on different games. However, if it is for some infrequent occurrences, it’s easy to do using chained events. • Q: is it possible to deploy callbacks on all characters sharing the same prototype? • A: not out of the box. This depends on individual settings in code. One can imagine that all characters of some type would share some events, but this is game-specific. Rooms of the same zone could share the same events as well. It is possible to do but requires modification of the source code. • Next tutorial: adding a voice-operated elevator with events.

13.21 A voice operated elevator using events

• Previous tutorial: Adding dialogues in events This tutorial will walk you through the steps to create a voice-operated elevator, using the in-game Python system. This tutorial assumes the in-game Python system is installed in your game. If it isn’t, you can follow the installation steps given in the documentation on in-game Python, and come back on this tutorial once the system is installed. You do not need to read the entire documentation, it’s a good reference, but not the easiest way to learn about it. Hence these tutorials.

366 Chapter 13. Tutorials Evennia Documentation, Release 0.9

The in-game Python system allows to run code on individual objects in some situations. You don’t have to modify the source code to add these features, past the installation. The entire system makes it easy to add specific features to some objects, but not all. What will we try to do? In this tutorial, we are going to create a simple voice-operated elevator. In terms of features, we will: • Explore events with parameters. • Work on more interesting callbacks. • Learn about chained events. • Play with variable modification in callbacks.

13.21.1 Our study case

Let’s summarize what we want to achieve first. We would like to create a room that will represent the inside of our elevator. In this room, a character could just say “1”, “2” or “3”, and the elevator will start moving. The doors will close and open on the new floor (the exits leading in and out of the elevator will be modified). We will work on basic features first, and then will adjust some, showing you how easy and powerfully independent actions can be configured through the in-game Python system.

13.21.2 Creating the rooms and exits we need

We’ll create an elevator right in our room (generally called “Limbo”, of ID 2). You could easily adapt the following instructions if you already have some rooms and exits, of course, just remember to check the IDs. Note: the in-game Python system uses IDs for a lot of things. While it is not mandatory, it is good practice to know the IDs you have for your callbacks, because it will make manipulation much quicker. There are other ways to identify objects, but as they depend on many factors, IDs are usually the safest path in our callbacks. Let’s go into limbo (#2) to add our elevator. We’ll add it to the north. To create this room, in-game you could type:

tunnel n= Inside of an elevator

The game should respond by telling you:

Created room Inside of an elevator(#3) of type typeclasses.rooms.Room. Created Exit from Limbo to Inside of an elevator: north(#4) (n). Created Exit back from Inside of an elevator to Limbo: south(#5) (s).

Note the given IDs: • #2 is limbo, the first room the system created. • #3 is our room inside of an elevator. • #4 is the north exit from Limbo to our elevator. • #5 is the south exit from an elevator to Limbo. Keep these IDs somewhere for the demonstration. You will shortly see why they are important. Why have we created exits to our elevator and back to Limbo? Isn’t the elevator supposed to move?

13.21. A voice operated elevator using events 367 Evennia Documentation, Release 0.9

It is. But we need to have exits that will represent the way inside the elevator and out. What we will do, at every floor, will be to change these exits so they become connected to the right room. You’ll see this process a bit later. We have two more rooms to create: our floor 2 and 3. This time, we’ll use dig, because we don’t need exits leading there, not yet anyway. dig The second floor dig The third floor

Evennia should answer with:

Created room The second floor(#6) of type typeclasses.rooms.Room. Created room The third floor(#7) of type typeclasses.rooms.Room.

Add these IDs to your list, we will use them too.

13.21.3 Our first callback in the elevator

Let’s go to the elevator (you could use tel #3 if you have the same IDs I have). This is our elevator room. It looks a bit empty, feel free to add a prettier description or other things to decorate it a bit. But what we want now is to be able to say “1”, “2” or “3” and have the elevator move in that direction. If you have read the previous tutorial about adding dialogues in events, you may remember what we need to do. If not, here’s a summary: we need to run some code when somebody speaks in the room. So we need to create a callback (the callback will contain our lines of code). We just need to know on which event this should be set. You can enter call here to see the possible events in this room. In the table, you should see the “say” event, which is called when somebody says something in the room. So we’ll need to add a callback to this event. Don’t worry if you’re a bit lost, just follow the following steps, the way they connect together will become more obvious. call/add here= say1,2,3

1. We need to add a callback. A callback contains the code that will be executed at a given time. So we use the call/add command and switch. 2. here is our object, the room in which we are. 3. An equal sign. 4. The name of the event to which the callback should be connected. Here, the event is “say”. Meaning this callback will be executed every time somebody says something in the room. 5. But we add an event parameter to indicate the keywords said in the room that should execute our callback. Otherwise, our callback would be called every time somebody speaks, no matter what. Here we limit, indicating our callback should be executed only if the spoken message contains “1”, “2” or “3”. An editor should open, inviting you to enter the Python code that should be executed. The first thing to remember is to read the text provided (it can contain important information) and, most of all, the list of variables that are available in this callback:

Variables you can use in this event:

character: the character having spoken in this room. room: the room connected to this event. message: the text having been spoken by the character.

(continues on next page)

368 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) ------Line Editor [Callback say of Inside of an elevator]------01| ------[l:01 w:000 c:0000]------(:h for help)------

This is important, in order to know what variables we can use in our callback out-of-the-box. Let’s write a single line to be sure our callback is called when we expect it to: character.msg("You just said {}.".format(message))

You can paste this line in-game, then type the :wq command to exit the editor and save your modifications. Let’s check. Try to say “hello” in the room. You should see the standard message, but nothing more. Now try to say “1”. Below the standard message, you should see:

You just said 1.

You can try it. Our callback is only called when we say “1”, “2” or “3”. Which is just what we want. Let’s go back in our code editor and add something more useful. call/edit here = say

Notice that we used the “edit” switch this time, since the callback exists, we just want to edit it.

The editor opens again. Let’s empty it first:

:DD

And turn off automatic indentation, which will help us:

:=

Auto-indentation is an interesting feature of the code editor, but we’d better not use it at this point, it will make copy/pasting more complicated.

13.21.4 Our entire callback in the elevator

So here’s the time to truly code our callback in-game. Here’s a little reminder: 1. We have all the IDs of our three rooms and two exits. 2. When we say “1”, “2” or “3”, the elevator should move to the right room, that is change the exits. Remember, we already have the exits, we just need to change their location and destination. It’s a good idea to try to write this callback yourself, but don’t feel bad about checking the solution right now. Here’s a possible code that you could paste in the code editor:

# First let's have some constants ELEVATOR= get(id=3) FLOORS={ "1": get(id=2), "2": get(id=6), "3": get(id=7), } (continues on next page)

13.21. A voice operated elevator using events 369 Evennia Documentation, Release 0.9

(continued from previous page) TO_EXIT= get(id=4) BACK_EXIT= get(id=5)

# Now we check that the elevator isn't already at this floor floor= FLOORS.get(message) if floor is None: character.msg("Which floor do you want?") elif TO_EXIT.location is floor: character.msg("The elevator already is at this floor.") else: # 'floor' contains the new room where the elevator should be room.msg_contents("The doors of the elevator close with a clank.") TO_EXIT.location= floor BACK_EXIT.destination= floor room.msg_contents("The doors of the elevator open to {floor}.", mapping=dict(floor=floor))

Let’s review this longer callback: 1. We first obtain the objects of both exits and our three floors. We use the get() eventfunc, which is a shortcut to obtaining objects. We usually use it to retrieve specific objects with an ID. We put the floors in a dictionary. The keys of the dictionary are the floor number (as str), the values are room objects. 2. Remember, the message variable contains the message spoken in the room. So either “1”, “2”, or “3”. We still need to check it, however, because if the character says something like “1 2” in the room, our callback will be executed. Let’s be sure what she says is a floor number. 3. We then check if the elevator is already at this floor. Notice that we use TO_EXIT.location. TO_EXIT contains our “north” exit, leading inside of our elevator. Therefore, its location will be the room where the elevator currently is. 4. If the floor is a different one, have the elevator “move”, changing just the location and destination of both exits. • The BACK_EXIT (that is “north”) should change its location. The elevator shouldn’t be accessible through our old floor. • The TO_EXIT (that is “south”, the exit leading out of the elevator) should have a different destination. When we go out of the elevator, we should find ourselves in the new floor, not the old one. Feel free to expand on this example, changing messages, making further checks. Usage and practice are keys. You can quit the editor as usual with :wq and test it out.

13.21.5 Adding a pause in our callback

Let’s improve our callback. One thing that’s worth adding would be a pause: for the time being, when we say the floor number in the elevator, the doors close and open right away. It would be better to have a pause of several seconds. More logical. This is a great opportunity to learn about chained events. Chained events are very useful to create pauses. Contrary to the events we have seen so far, chained events aren’t called automatically. They must be called by you, and can be called after some time. • Chained events always have the name “chain_X”. Usually, X is a number, but you can give the chained event a more explicit name. • In our original callback, we will call our chained events in, say, 15 seconds. • We’ll also have to make sure the elevator isn’t already moving.

370 Chapter 13. Tutorials Evennia Documentation, Release 0.9

Other than that, a chained event can be connected to a callback as usual. We’ll create a chained event in our elevator, that will only contain the code necessary to open the doors to the new floor. call/add here= chain_1

The callback is added to the “chain_1” event, an event that will not be automatically called by the system when something happens. Inside this event, you can paste the code to open the doors at the new floor. You can notice a few differences:

TO_EXIT.location= floor TO_EXIT.destination= ELEVATOR BACK_EXIT.location= ELEVATOR BACK_EXIT.destination= floor room.msg_contents("The doors of the elevator open to {floor}.", mapping=dict(floor=floor))

Paste this code into the editor, then use :wq to save and quit the editor. Now let’s edit our callback in the “say” event. We’ll have to change it a bit: • The callback will have to check the elevator isn’t already moving. • It must change the exits when the elevator move. • It has to call the “chain_1” event we have defined. It should call it 15 seconds later. Let’s see the code in our callback. call/edit here= say

Remove the current code and disable auto-indentation again:

:DD :=

And you can paste instead the following code. Notice the differences with our first attempt:

# First let's have some constants ELEVATOR= get(id=3) FLOORS={ "1": get(id=2), "2": get(id=6), "3": get(id=7), } TO_EXIT= get(id=4) BACK_EXIT= get(id=5)

# Now we check that the elevator isn't already at this floor floor= FLOORS.get(message) if floor is None: character.msg("Which floor do you want?") elif BACK_EXIT.location is None: character.msg("The elevator is between floors.") elif TO_EXIT.location is floor: character.msg("The elevator already is at this floor.") else: # 'floor' contains the new room where the elevator should be room.msg_contents("The doors of the elevator close with a clank.") TO_EXIT.location= None (continues on next page)

13.21. A voice operated elevator using events 371 Evennia Documentation, Release 0.9

(continued from previous page) BACK_EXIT.location= None call_event(room,"chain_1", 15)

What changed? 1. We added a little test to make sure the elevator wasn’t already moving. If it is, the BACK_EXIT.location (the “south” exit leading out of the elevator) should be None. We’ll remove the exit while the elevator is moving. 2. When the doors close, we set both exits’ location to None. Which “removes” them from their room but doesn’t destroy them. The exits still exist but they don’t connect anything. If you say “2” in the elevator and look around while the elevator is moving, you won’t see any exits. 3. Instead of opening the doors immediately, we call call_event. We give it the object containing the event to be called (here, our elevator), the name of the event to be called (here, “chain_1”) and the number of seconds from now when the event should be called (here, 15). 4. The chain_1 callback we have created contains the code to “re-open” the elevator doors. That is, besides displaying a message, it reset the exits’ location and destination. If you try to say “3” in the elevator, you should see the doors closing. Look around you and you won’t see any exit. Then, 15 seconds later, the doors should open, and you can leave the elevator to go to the third floor. While the elevator is moving, the exit leading to it will be inaccessible. Note: we don’t define the variables again in our chained event, we just call them. When we execute call_event, a copy of our current variables is placed in the database. These variables will be restored and accessible again when the chained event is called. You can use the call/tasks command to see the tasks waiting to be executed. For instance, say “2” in the room, notice the doors closing, and then type the call/tasks command. You will see a task in the elevator, waiting to call the chain_1 event.

13.21.6 Changing exit messages

Here’s another nice little feature of events: you can modify the message of a single exit without altering the others. In this case, when someone goes north into our elevator, we’d like to see something like: “someone walks into the elevator.” Something similar for the back exit would be great too. Inside of the elevator, you can look at the available events on the exit leading outside (south).

call south

You should see two interesting rows in this table:

| msg_arrive|0(0)| Customize the message when a character| ||| arrives through this exit.| | msg_leave|0(0)| Customize the message when a character leaves| ||| through this exit.|

So we can change the message others see when a character leaves, by editing the “msg_leave” event. Let’s do that:

call/add south= msg_leave

Take the time to read the help. It gives you all the information you should need. We’ll need to change the “message” variable, and use custom mapping (between braces) to alter the message. We’re given an example, let’s use it. In the code editor, you can paste the following line:

372 Chapter 13. Tutorials Evennia Documentation, Release 0.9

message=" {character} walks out of the elevator."

Again, save and quit the editor by entering :wq. You can create a new character to see it leave. charcreate A beggar tel #8 = here

(Obviously, adapt the ID if necessary.) py self.search("beggar").move_to(self.search("south"))

This is a crude way to force our beggar out of the elevator, but it allows us to test. You should see:

A beggar(#8) walks out of the elevator.

Great! Let’s do the same thing for the exit leading inside of the elevator. Follow the beggar, then edit “msg_leave” of “north”: call/add north= msg_leave message=" {character} walks into the elevator."

Again, you can force our beggar to move and see the message we have just set. This modification applies to these two exits, obviously: the custom message won’t be used for other exits. Since we use the same exits for every floor, this will be available no matter at what floor the elevator is, which is pretty neat!

13.21.7 Tutorial F.A.Q.

• Q: what happens if the game reloads or shuts down while a task is waiting to happen? • A: if your game reloads while a task is in pause (like our elevator between floors), when the game is accessible again, the task will be called (if necessary, with a new time difference to take into account the reload). If the server shuts down, obviously, the task will not be called, but will be stored and executed when the server is up again. • Q: can I use all kinds of variables in my callback? Whether chained or not? • A: you can use every variable type you like in your original callback. However, if you execute call_event, since your variables are stored in the database, they will need to respect the constraints on persistent attributes. A callback will not be stored in this way, for instance. This variable will not be available in your chained event. • Q: when you say I can call my chained events something else than “chain_1”, “chain_2” and such, what is the naming convention? • A: chained events have names beginning by “chain_”. This is useful for you and for the system. But after the underscore, you can give a more useful name, like “chain_open_doors” in our case. • Q: do I have to pause several seconds to call a chained event? • A: no, you can call it right away. Just leave the third parameter of call_event out (it will default to 0, meaning the chained event will be called right away). This will not create a task. • Q: can I have chained events calling themselves? • A: you can. There’s no limitation. Just be careful, a callback that calls itself, particularly without delay, might be a good recipe for an infinite loop. However, in some cases, it is useful to have chained events calling themselves, to do the same repeated action every X seconds for instance. • Q: what if I need several elevators, do I need to copy/paste these callbacks each time?

13.21. A voice operated elevator using events 373 Evennia Documentation, Release 0.9

• A: not advisable. There are definitely better ways to handle this situation. One of them is to consider adding the code in the source itself. Another possibility is to call chained events with the expected behavior, which makes porting code very easy. This side of chained events will be shown in the next tutorial. • Previous tutorial: Adding dialogues in events

13.22 Add a simple new web page

Evennia leverages Django 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.

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.

13.22.1 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 cool stuff with the page in the view, like adding dynamic content or change it on the fly. Open mygame/web folder and add a new module there named story.py (you could also put it in its own folder if you wanted to be neat. Don’t forget to add an empty __init__.py file if you do, to tell Python you can import from the new folder). Here’s how it looks:

# in mygame/web/story.py

from django.shortcuts import render

def storypage(request): return render(request,"story.html")

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.

13.22.2 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.

374 Chapter 13. Tutorials Evennia Documentation, Release 0.9

This is not a HTML tutorial, so we’ll go simple:

{% extends"base.html"%} {% block content%}

A story about a tree

This is a story about a tree, a classic tale...

{% endblock%}

Since we’ve used the render shortcut, Django will allow us to extend our base styles easily. If you’d rather not take advantage of Evennia’s base styles, you can do something like this instead:

A story about a tree

This is a story about a tree, a classic tale...

13.22.3 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. 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 how it could look:

from web import story

# ...

custom_patterns=[ url(r'story', story.storypage, name='Story'), ]

That is, we import our story view module from where we created it earlier and then create an url instance. The first argument to url is the pattern of the url we want to find ("story") (this is a regular expression if you are familiar with those) and then our view function we want to direct to.

13.22. Add a simple new web page 375 Evennia Documentation, Release 0.9

That should be it. Reload Evennia and you should be able to browse to your new story page!

13.23 Web Tutorial

Evennia uses the Django web framework as the basis of both its database configuration and the website it provides. While a full understanding of Django requires reading the Django documentation, we have provided this tutorial to get you running with the basics and how they pertain to Evennia. This text details getting everything set up. The Web-based Character view Tutorial 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.

13.23.1 A Basic Overview

Django is a web framework. It gives you a set of development tools for building a website quickly and easily. Django projects are split up into apps and these apps all contribute to one project. For instance, you might have an app for conducting polls, or an app for showing news posts or, like us, one for creating a web client. Each of these applications has a urls.py file, which specifies what URLs are used by the app, a views.py file for the code that the URLs activate, a templates directory for displaying the results of that code in HTML for the user, and a static folder that holds assets like CSS, Javascript, and Image files (You may note your mygame/web folder does not have a static or template folder. This is intended and explained further below). Django applications may also have a models.py file for storing information in the database. We will not change any models here, take a look at the New Models page (as well as the Django docs 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 default URLs for you. This is located in web/urls.py.

13.23.2 Changing the logo on the front page

Evennia’s default logo is a fun little googly-eyed snake wrapped around a gear globe. As cute as it is, it probably doesn’t represent your game. So one of the first things you may wish to do is replace it with a logo of your own. Django web apps all have static assets: CSS files, Javascript files, and Image files. In order to make sure the final project has all the static files it needs, the system collects the files from every app’s static folder and places it in the STATIC_ROOT defined in settings.py. By default, the Evennia STATIC_ROOT is in web/static. Because Django pulls files from all of those separate places and puts them in one folder, it’s possible for one file to overwrite another. We will use this to plug in our own files without having to change anything in the Evennia itself. By default, Evennia is configured to pull files you put in the web/static_overrides after all other static files. That means that files in static_overrides folder will overwrite any previously loaded files having the same path under its static folder. This last part is important to repeat: To overload the static resource from a standard static folder you need to replicate the path of folders and file names from that static folder in exactly the same way inside static_overrides. Let’s see how this works for our logo. The default web application is in the Evennia library itself, in evennia/web/. We can see that there is a static folder here. If we browse down, we’ll eventually find the full path to the Evennia logo file: evennia/web/static/evennia_general/images/evennia_logo.png. Inside our static_overrides we must replicate the part of the path inside the static folder, in other words, we must replicate evennia_general/images/evennia_logo.png. So, to change the logo, we need to create the folder path evennia_general/images/ in static_overrides. We then rename our own logo file to evennia_logo.png and copy it there. The final path for this file would

376 Chapter 13. Tutorials Evennia Documentation, Release 0.9

thus be: web/static_overrides/evennia_general/images/evennia_logo.png in your local game folder. To get this file pulled in, just change to your own game directory and reload the server:

evennia reload

This will reload the configuration and bring in the new static file(s). If you didn’t want to reload the server you could instead use

evennia collectstatic

to only update the static files without any other changes. Note: Evennia will collect static files automatically during startup. So if evennia collectstatic reports finding 0 files to collect, make sure you didn’t start the engine at some point - if so the collector has already done its work! To make sure, connect to the website and check so the logo has actually changed to your own version. Note: Sometimes the static asset collector can get confused. If no matter what you do, your overridden files aren’t getting copied over the defaults, try removing the target file (or everything) in the web/ static directory, and re-running collectstatic to gather everything from scratch.

13.23.3 Changing the Front Page’s Text

The default front page for Evennia contains information about the Evennia project. You’ll probably want to replace this information with information about your own project. Changing the page template is done in a similar way to changing static resources. Like static files, Django looks through a series of template folders to find the file it wants. The difference is that Django does not copy all of the template files into one place, it just searches through the template folders until it finds a template that matches what it’s looking for. This means that when you edit a template, the changes are instant. You don’t have to reload the server or run any extra commands to see these changes - reloading the web page in your browser is enough. To replace the index page’s text, we’ll need to find the template for it. We’ll go into more detail about how to determine which template is used for rendering a page in the Web-based Character view Tutorial. For now, you should know that the template we want to change is stored in evennia/web/website/templates/website/index.html. To replace this template file, you will put your changed template inside the web/template_overrides/ website directory in your game folder. In the same way as with static resources you must replicate the path inside the default template directory exactly. So we must copy our replacement template named index.html there (or create the website directory in web/template_overridesif it does not exist, first). The final path to the file should thus be:web/template_overrides/website/index.html‘ within your game direc- tory. Note that it is usually easier to just copy the original template over and edit it in place. The original file already has all the markup and tags, ready for editing.

13.23.4 Further reading

For further hints on working with the web presence, you could now continue to the Web-based Character view Tutorial where you learn to make a web page that displays in-game character stats. You can also look at Django’s own tutorial to get more insight in how Django works and what possibilities exist.

13.23. Web Tutorial 377 Evennia Documentation, Release 0.9

13.24 Web Character View Tutorial

Before doing this tutorial you will probably want to read the intro in ‘Basic Web tutorial‘_. In this tutorial we will create a web page that displays the stats of a game character. For this, and all other pages we want to make specific to our game, we’ll need to create our own Django “app” We’ll call our app character, since it will be dealing with character information. From your game dir, run

evennia startapp character

This will create a directory named character in the root of your game dir. It contains all basic files that a Django app needs. To keep mygame well ordered, move it to your mygame/web/ directory instead:

mv character web/

Note that we will not edit all files in this new directory, many of the generated files are outside the scope of this tutorial. In order for Django to find our new web app, we’ll need to add it to the INSTALLED_APPS setting. Evennia’s default installed apps are already set, so in server/conf/settings.py, we’ll just extend them:

INSTALLED_APPS+=('web.character',)

Note: That end comma is important. It makes sure that Python interprets the addition as a tuple instead of a string.

The first thing we need to do is to create a view and an URL pattern to point to it. A view is a function that generates the web page that a visitor wants to see, while the URL pattern lets Django know what URL should trigger the view. The pattern may also provide some information of its own as we shall see. Here is our character/urls.py file (Note: you may have to create this file if a blank one wasn’t generated for you):

# URL patterns for the character app

from django.conf.urls import url from web.character.views import sheet

urlpatterns=[ url(r'^sheet/(?P\d+)/$', sheet, name="sheet") ]

This file contains all of the URL patterns for the application. The url function in the urlpatterns list are given three arguments. The first argument is a pattern-string used to identify which URLs are valid. Patterns are specified as regular expressions. Regular expressions are used to match strings and are written in a special, very compact, syntax. A detailed description of regular expressions is beyond this tutorial but you can learn more about them here. For now, just accept that this regular expression requires that the visitor’s URL looks something like this:

sheet/123/

That is, sheet/ followed by a number, rather than some other possible URL pattern. We will interpret this number as object ID. Thanks to how the regular expression is formulated, the pattern recognizer stores the number in a variable called object_id. This will be passed to the view (see below). We add the imported view function (sheet) in the second argument. We also add the name keyword to identify the URL pattern itself. You should always name your URL patterns, this makes them easy to refer to in html templates using the {% url %} tag (but we won’t get more into that in this tutorial).

378 Chapter 13. Tutorials Evennia Documentation, Release 0.9

Security Note: Normally, users do not have the ability to see object IDs within the game (it’s restricted to superusers only). Exposing the game’s object IDs to the public like this enables to perform what is known as an account enumeration attack in the efforts of hijacking your superuser account. Consider this: in every Evennia installation, there are two objects that we can always expect to exist and have the same object IDs– Limbo (#2) and the superuser you create in the beginning (#1). Thus, the can get 50% of the information they need to hijack the admin account (the admin’s username) just by navigating to sheet/1! Next we create views.py, the view file that urls.py refers to.

# Views for our character app

from django.http import Http404 from django.shortcuts import render from django.conf import settings

from evennia.utils.search import object_search from evennia.utils.utils import inherits_from

def sheet(request, object_id): object_id='#'+ object_id try: character= object_search(object_id)[0] except IndexError: raise Http404("I couldn't find a character with that ID.") if not inherits_from(character, settings.BASE_CHARACTER_TYPECLASS): raise Http404("I couldn't find a character with that ID." "Found something else instead.") return render(request,'character/sheet.html',{'character': character})

As explained earlier, the URL pattern parser in urls.py parses the URL and passes object_id to our view function sheet. We do a database search for the object using this number. We also make sure such an object exists and that it is actually a Character. The view function is also handed a request object. This gives us information about the request, such as if a logged-in user viewed it - we won’t use that information here but it is good to keep in mind. On the last line, we call the render function. Apart from the request object, the render function takes a path to an html template and a dictionary with extra data you want to pass into said template. As extra data we pass the Character object we just found. In the template it will be available as the variable “character”. The html template is created as templates/character/sheet.html under your character app folder. You may have to manually create both template and its subfolder character. Here’s the template to create:

{% extends "base.html" %} {% block content %}

{{ character.name }}

{{ character.db.desc }}

Stats

(continues on next page)

13.24. Web Character View Tutorial 379 Evennia Documentation, Release 0.9

(continued from previous page)

Stat Value
Strength {{ character.db.str }}
Intelligence {{ character.db.int }}
Speed {{ character.db.spd }}

Skills

    {% for skill in character.db.skills %}
  • {{ skill }}
  • {% empty %}
  • This character has no skills yet.
  • {% endfor %}

{% if character.db.approved %}

This character has been approved!

{% else %}

This character has not yet been approved!

{% endif %} {% endblock %}

In Django templates, {% ... %} denotes special in-template “functions” that Django understands. The {{ ... }} blocks work as “slots”. They are replaced with whatever value the code inside the block returns. The first line, {% extends "base.html" %}, tells Django that this template extends the base template that Evennia is using. The base template is provided by the theme. Evennia comes with the open-source third-party theme prosimii. You can find it and its base.html in evennia/web/templates/prosimii. Like other templates, these can be overwritten. The next line is {% block content %}. The base.html file has blocks, which are placeholders that tem- plates can extend. The main block, and the one we use, is named content. We can access the character variable anywhere in the template because we passed it in the render call at the end of view.py. That means we also have access to the Character’s db attributes, much like you would in normal Python code. You don’t have the ability to call functions with arguments in the template– in fact, if you need to do any complicated logic, you should do it in view.py and pass the results as more variables to the template. But you still have a great deal of flexibility in how you display the data. We can do a little bit of logic here as well. We use the {% for %} ... {% endfor %} and {% if %} ... {% else %} ... {% endif %} structures to change how the template renders depending on how many skills the user has, or if the user is approved (assuming your game has an approval system). The last file we need to edit is the master URLs file. This is needed in order to smoothly integrate the URLs from your new character app with the URLs from Evennia’s existing pages. Find the file web/urls.py and update its patterns list as follows:

380 Chapter 13. Tutorials Evennia Documentation, Release 0.9

# web/urls.py

custom_patterns=[ url(r'^character/', include('web.character.urls')) ]

Now reload the server with evennia reload and visit the page in your browser. If you haven’t changed your defaults, you should be able to find the sheet for character #1 at http://localhost:4001/character/ sheet/1/ Try updating the stats in-game and refresh the page in your browser. The results should show immediately. As an optional final step, you can also change your character typeclass to have a method called ‘get_absolute_url’.

# typeclasses/characters.py

# inside Character def get_absolute_url(self): from django.urls import reverse return reverse('character:sheet', kwargs={'object_id':self.id})

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. 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‘_.

13.25 Help System Tutorial

Before doing this tutorial you will probably want to read the intro in ‘Basic Web tutorial‘_. Reading the three first parts of the Django tutorial might help as well. This tutorial will show you how to access the help system through your website. Both help commands and regular help entries will be visible, depending on the logged-in user or an anonymous character. This tutorial will show you how to: • Create a new page to add to your website. • Take advantage of a basic view and basic templates. • Access the help system on your website. • Identify whether the viewer of this page is logged-in and, if so, to what account.

13.25.1 Creating our app

The first step is to create our new Django app. An app in Django can contain pages and mechanisms: your website may contain different apps. Actually, the website provided out-of-the-box by Evennia has already three apps: a “webclient” app, to handle the entire webclient, a “website” app to contain your basic pages, and a third app provided by Django to create a simple admin interface. So we’ll create another app in parallel, giving it a clear name to represent our help system. From your game directory, use the following command:

13.25. Help System Tutorial 381 Evennia Documentation, Release 0.9

evennia startapp help_system

Note: calling the app “help” would have been more explicit, but this name is already used by Django.

This will create a directory named help_system at the root of your game directory. It’s a good idea to keep things organized and move this directory in the “web” directory of your game. Your game directory should look like:

mygame/ ... web/ help_system/ ...

The “web/help_system” directory contains files created by Django. We’ll use some of them, but if you want to learn more about them all, you should read the Django tutorial. There is a last thing to be done: your folder has been added, but Django doesn’t know about it, it doesn’t know it’s a new app. We need to tell it, and we do so by editing a simple setting. Open your “server/conf/settings.py” file and add, or edit, these lines:

# Web configuration INSTALLED_APPS+=( "web.help_system", )

You can start Evennia if you want, and go to your website, probably at http://localhost:4001 . You won’t see anything different though: we added the app but it’s fairly empty.

13.25.2 Our new page

At this point, our new app contains mostly empty files that you can explore. In order to create a page for our help system, we need to add: •A view, dealing with the logic of our page. •A template to display our new page. • A new URL pointing to our page. We could get away by creating just a view and a new URL, but that’s not a recommended way to work with your website. Building on templates is so much more convenient.

Create a view

A view in Django is a simple Python function placed in the “views.py” file in your app. It will handle the behavior that is triggered when a user asks for this information by entering a URL (the connection between views and URLs will be discussed later). So let’s create our view. You can open the “web/help_system/view.py” file and paste the following lines:

from django.shortcuts import render

def index(request): """The 'index' view.""" return render(request,"help_system/index.html")

382 Chapter 13. Tutorials Evennia Documentation, Release 0.9

Our view handles all code logic. This time, there’s not much: when this function is called, it will render the template we will now create. But that’s where we will do most of our work afterward.

Create a template

The render function called into our view asks the template help_system/index.html. The templates of our apps are stored in the app directory, “templates” sub-directory. Django may have created the “templates” folder already. If not, create it yourself. In it, create another folder “help_system”, and inside of this folder, create a file named “index.html”. Wow, that’s some hierarchy. Your directory structure (starting from web) should look like this: web/ help_system/ ... templates/ help_system/ index.html

Open the “index.html” file and paste in the following lines:

{% extends"base.html"%} {% block titleblock%}Help index{% endblock%} {% block content%}

Help index

{% endblock%}

Here’s a little explanation line by line of what this template does: 1. It loads the “base.html” template. This describes the basic structure of all your pages, with a menu at the top and a footer, and perhaps other information like images and things to be present on each page. You can create templates that do not inherit from “base.html”, but you should have a good reason for doing so. 2. The “base.html” template defines all the structure of the page. What is left is to override some sections of our pages. These sections are called blocks. On line 2, we override the block named “blocktitle”, which contains the title of our page. 3. Same thing here, we override the block named “content”, which contains the main content of our web page. This block is bigger, so we define it on several lines. 4. This is perfectly normal HTML code to display a level-2 heading. 5. And finally we close the block named “content”.

Create a new URL

Last step to add our page: we need to add a URL leading to it. . . otherwise users won’t be able to access it. The URLs of our apps are stored in the app’s directory “urls.py” file. Open the “web/help_system/urls.py” file (you might have to create it) and write in it:

# URL patterns for the help_system app from django.conf.urls import url from web.help_system.views import index urlpatterns=[ url(r'^$', index, name="index") ]

13.25. Help System Tutorial 383 Evennia Documentation, Release 0.9

We also need to add our app as a namespace holder for URLS. Edit the file “web/urls.py”. In it you will find the custom_patterns variable. Replace it with: custom_patterns=[ url(r'^help/', include('web.help_system.urls', namespace='help_system', app_name='help_system')), ]

When a user will ask for a specific URL on your site, Django will: 1. Read the list of custom patterns defined in “web/urls.py”. There’s one pattern here, which describes to Django that all URLs beginning by ‘help/’ should be sent to the ‘help_system’ app. The ‘help/’ part is removed. 2. Then Django will check the “web.help_system/urls.py” file. It contains only one URL, which is empty (^$). In other words, if the URL is ‘/help/’, then Django will execute our defined view.

Let’s see it work

You can now reload or start Evennia. Open a tab in your browser and go to http://localhost:4001/help/ . If everything goes well, you should see your new page. . . which isn’t empty since Evennia uses our “base.html” template. In the content of our page, there’s only a heading that reads “help index”. Notice that the title of our page is “mygame - Help index” (“mygame” is replaced by the name of your game). From now on, it will be easier to move forward and add features.

A brief reminder

We’ll be trying the following things: • Have the help of commands and help entries accessed online. • Have various commands and help entries depending on whether the user is logged in or not. In terms of pages, we’ll have: • One to display the list of help topics. • One to display the content of a help topic. The first one would link to the second. Should we create two URLs? The answer is. . . maybe. It depends on what you want to do. We have our help index accessible through the “/help/” URL. We could have the detail of a help entry accessible through “/help/desc” (to see the detail of the “desc” com- mand). The problem is that our commands or help topics may contain special characters that aren’t to be present in URLs. There are different ways around this problem. I have decided to use a GET variable here, which would create URLs like this:

/help?name=desc

If you use this system, you don’t have to add a new URL: GET and POST variables are accessible through our requests and we’ll see how soon enough.

384 Chapter 13. Tutorials Evennia Documentation, Release 0.9

13.25.3 Handling logged-in users

One of our requirements is to have a help system tailored to our accounts. If an account with admin access logs in, the page should display a lot of commands that aren’t accessible to common users. And perhaps even some additional help topics. Fortunately, it’s fairly easy to get the logged in account in our view (remember that we’ll do most of our coding there). The request object, passed to our function, contains a user attribute. This attribute will always be there: we cannot test whether it’s None or not, for instance. But when the request comes from a user that isn’t logged in, the user attribute will contain an anonymous Django user. We then can use the is_anonymous method to see whether the user is logged-in or not. Last gift by Evennia, if the user is logged in, request.user contains a reference to an account object, which will help us a lot in coupling the game and online system. So we might end up with something like:

def index(request): """The 'index' view.""" user = request.user if not user.is_anonymous() and user.character: character = user.character

Note: this code works when your MULTISESSION\_MODE is set to 0 or 1. When it’s above, you would have something like:

def index(request): """The 'index' view.""" user= request.user if not user.is_anonymous() and user.db._playable_characters: character= user.db._playable_characters[0]

In this second case, it will select the first character of the account. But what if the user’s not logged in? Again, we have different solutions. One of the most simple is to create a character that will behave as our default character for the help system. You can create it through your game: connect to it and enter:

@charcreate anonymous

The system should answer:

Created new character anonymous. Use @ic anonymous to enter the game as this

˓→character.

So in our view, we could have something like this:

from typeclasses.characters import Character def index(request): """The 'index' view.""" user= request.user if not user.is_anonymous() and user.character: character= user.character else: character= Character.objects.get(db_key="anonymous")

This time, we have a valid character no matter what: remember to adapt this code if you’re running in multisession mode above 1.

13.25. Help System Tutorial 385 Evennia Documentation, Release 0.9

13.25.4 The full system

What we’re going to do is to browse through all commands and help entries, and list all the commands that can be seen by this character (either our ‘anonymous’ character, or our logged-in character). The code is longer, but it presents the entire concept in our view. Edit the “web/help_system/views.py” file and paste into it: from django.http import Http404 from django.shortcuts import render from evennia.help.models import HelpEntry from typeclasses.characters import Character def index(request): """The 'index' view.""" user= request.user if not user.is_anonymous() and user.character: character= user.character else: character= Character.objects.get(db_key="anonymous")

# Get the categories and topics accessible to this character categories, topics= _get_topics(character)

# If we have the 'name' in our GET variable topic= request.GET.get("name") if topic: if topic not in topics: raise Http404("This help topic doesn't exist.")

topic= topics[topic] context={ "character": character, "topic": topic, } return render(request,"help_system/detail.html", context) else: context={ "character": character, "categories": categories, } return render(request,"help_system/index.html", context) def _get_topics(character): """Return the categories and topics for this character.""" cmdset= character.cmdset.all()[0] commands= cmdset.commands entries= [entry for entry in HelpEntry.objects.all()] categories={} topics={}

# Browse commands for command in commands: if not command.auto_help or not command.access(character): continue

# Create the template for a command (continues on next page)

386 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) template={ "name": command.key, "category": command.help_category, "content": command.get_help(character, cmdset), }

category= command.help_category if category not in categories: categories[category]=[] categories[category].append(template) topics[command.key]= template

# Browse through the help entries for entry in entries: if not entry.access(character,'view', default= True): continue

# Create the template for an entry template={ "name": entry.key, "category": entry.help_category, "content": entry.entrytext, }

category= entry.help_category if category not in categories: categories[category]=[] categories[category].append(template) topics[entry.key]= template

# Sort categories for entries in categories.values(): entries.sort(key=lambda c: c["name"])

categories= list(sorted(categories.items())) return categories, topics

That’s a bit more complicated here, but all in all, it can be divided in small chunks: • The index function is our view: • It begins by getting the character as we saw in the previous section. • It gets the help topics (commands and help entries) accessible to this character. It’s another function that handles that part. • If there’s a GET variable “name” in our URL (like “/help?name=drop”), it will retrieve it. If it’s not a valid topic’s name, it returns a 404. Otherwise, it renders the template called “detail.html”, to display the detail of our topic. • If there’s no GET variable “name”, render “index.html”, to display the list of topics. • The _get_topics is a private function. Its sole mission is to retrieve the commands a character can execute, and the help entries this same character can see. This code is more Evennia-specific than Django-specific, it will not be detailed in this tutorial. Just notice that all help topics are stored in a dictionary. This is to simplify our job when displaying them in our templates. Notice that, in both cases when we asked to render a template, we passed to render a third argument which is the dictionary of variables used in our templates. We can pass variables this way, and we will use them in our templates.

13.25. Help System Tutorial 387 Evennia Documentation, Release 0.9

The index template

Let’s look at our full “index” template. You can open the “web/help_system/templates/help_sstem/index.html” file and paste the following into it:

{% extends"base.html"%} {% block titleblock%}Help index{% endblock%} {% block content%}

Help index

{% if categories%} {% for category, topics in categories%}

{{ category|capfirst }}

{% for topic in topics%} {% if forloop.counter|divisibleby:"5"%} {% endif%} {% endfor%}
{{ topic.name }}
{% endfor%} {% endif%} {% endblock%}

This template is definitely more detailed. What it does is: 1. Browse through all categories. 2. For all categories, display a level-2 heading with the name of the category. 3. All topics in a category (remember, they can be either commands or help entries) are displayed in a table. The trickier part may be that, when the loop is above 5, it will create a new line. The table will have 5 columns at the most per row. 4. For every cell in the table, we create a link redirecting to the detail page (see below). The URL would look something like “help?name=say”. We use urlencode to ensure special characters are properly escaped.

The detail template

It’s now time to show the detail of a topic (command or help entry). You can create the file “web/help_system/templates/help_system/detail.html”. You can paste into it the following code:

{% extends"base.html"%} {% block titleblock%}Help for {{ topic.name }}{% endblock%} {% block content%}

{{ topic.name|capfirst }} help topic

Category: {{ topic.category|capfirst }}

{{ topic.content|linebreaks }} {% endblock%}

This template is much easier to read. Some filters might be unknown to you, but they are just used to format here.

388 Chapter 13. Tutorials Evennia Documentation, Release 0.9

Put it all together

Remember to reload or start Evennia, and then go to http://localhost:4001/help. You should see the list of commands and topics accessible by all characters. Try to login (click the “login” link in the menu of your website) and go to the same page again. You should now see a more detailed list of commands and help entries. Click on one to see its detail.

13.25.5 To improve this feature

As always, a tutorial is here to help you feel comfortable adding new features and code by yourself. Here are some ideas of things to improve this little feature: • Links at the bottom of the detail template to go back to the index might be useful. • A link in the main menu to link to this page would be great. . . for the time being you have to enter the URL, users won’t guess it’s there. • Colors aren’t handled at this point, which isn’t exactly surprising. You could add it though. • Linking help entries between one another won’t be simple, but it would be great. For instance, if you see a help entry about how to use several commands, it would be great if these commands were themselves links to display their details.

13.26 Add a wiki on your website

Before doing this tutorial you will probably want to read the intro in ‘Basic Web tutorial‘_. Reading the three first parts of the Django tutorial might help as well.

This tutorial will provide a step-by-step process to installing a wiki on your website. Fortunately, you don’t have to create the features manually, since it has been done by others, and we can integrate their work quite easily with Django. I have decided to focus on the Django-wiki.

Note: this article has been updated for Evennia 0.9. If you’re not yet using this version, be careful, as the django wiki doesn’t support Python 2 anymore. (Remove this note when enough time has passed.)

The Django-wiki offers a lot of features associated with wikis, is actively maintained (at this time, anyway), and isn’t too difficult to install in Evennia. You can see a demonstration of Django-wiki here.

13.26.1 Basic installation

You should begin by shutting down the Evennia server if it is running. We will run migrations and alter the virtual environment just a bit. Open a terminal and activate your Python environment, the one you use to run the evennia command.

• On Linux:

13.26. Add a wiki on your website 389 Evennia Documentation, Release 0.9

source evenv/bin/activate

• Or Windows:

evenv\bin\activate

Installing with pip

Install the wiki using pip: pip install wiki

Note: this will install the last version of Django wiki. Version >0.4 doesn’t support Python 2, so install wiki 0.3 if you haven’t updated to Python 3 yet.

It might take some time, the Django-wiki having some dependencies.

Adding the wiki in the settings

You will need to add a few settings to have the wiki app on your website. Open your server/conf/settings.py file and add the following at the bottom (but before importing secret_settings). Here’s what you’ll find in my own setting file (add the whole Django-wiki section):

r""" Evennia settings file.

...

"""

# Use the defaults from Evennia unless explicitly overridden from evennia.settings_default import *

###################################################################### # Evennia base server config ######################################################################

# This is the name of your game. Make it catchy! SERVERNAME="demowiki"

###################################################################### # Django-wiki settings ###################################################################### INSTALLED_APPS+=( 'django.contrib.humanize.apps.HumanizeConfig', 'django_nyt.apps.DjangoNytConfig', 'mptt', 'sorl.thumbnail', 'wiki.apps.WikiConfig', 'wiki.plugins.attachments.apps.AttachmentsConfig', 'wiki.plugins.notifications.apps.NotificationsConfig', 'wiki.plugins.images.apps.ImagesConfig', (continues on next page)

390 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) 'wiki.plugins.macros.apps.MacrosConfig', )

# Disable wiki handling of login/signup WIKI_ACCOUNT_HANDLING= False WIKI_ACCOUNT_SIGNUP_ALLOWED= False

###################################################################### # Settings given in secret_settings.py override those in this file. ###################################################################### try: from server.conf.secret_settings import * except ImportError: print("secret_settings.py file not found or failed to import.")

Adding the new URLs

Next we need to add two URLs in our web/urls.py file. Open it and compare the following output: you will need to add two URLs in custom_patterns and add one import line:

from django.conf.urls import url, include from django.urls import path # NEW!

# default evenni a patterns from evennia.web.urls import urlpatterns

# eventual custom patterns custom_patterns=[ # url(r'/desired/url/', view, name='example'), url('notifications/', include('django_nyt.urls')), # NEW! url('wiki/', include('wiki.urls')), # NEW! ]

# this is required by Django. urlpatterns= custom_patterns+ urlpatterns

You will probably need to copy line 2, 10, and 11. Be sure to place them correctly, as shown in the example above.

Running migrations

It’s time to run the new migrations. The wiki app adds a few tables in our database. We’ll need to run:

evennia migrate

And that’s it, you can start the server. If you go to http://localhost:4001/wiki , you should see

13.26. Add a wiki on your website 391 Evennia Documentation, Release 0.9

the wiki. Use your account’s username and password to connect to it. That’s how simple it is.

13.26.2 Customizing privileges

A wiki can be a great collaborative tool, but who can see it? Who can modify it? Django-wiki comes with a privilege system centered around four values per wiki page. The owner of an article can always read and write in it (which is somewhat logical). The group of the article defines who can read and who can write, if the user seeing the page belongs to this group. The topic of groups in wiki pages will not be discussed here. A last setting determines which other user (that is, these who aren’t in the groups, and aren’t the article’s owner) can read and write. Each article has these four settings (group read, group write, other read, other write). Depending on your purpose, it might not be a good default choice, particularly if you have to remind every builder to keep the pages private. Fortunately, Django-wiki gives us additional settings to customize who can read, and who can write, a specific article.

These settings must be placed, as usual, in your server/conf/settings.py file. They take a function as argument, said function (or callback) will be called with the article and the user. Remember, a Django user, for us, is an account. So we could check lockstrings on them if needed. Here is a default setting to restrict the wiki: only builders can write in it, but anyone (including non-logged in users) can read it. The superuser has some additional privileges.

# In server/conf/settings.py # ...

def is_superuser(article, user): """Return True if user is a superuser, False otherwise.""" return not user.is_anonymous() and user.is_superuser

def is_builder(article, user): """Return True if user is a builder, False otherwise.""" return not user.is_anonymous() and user.locks.check_lockstring(user,

˓→"perm(Builders)")

def is_anyone(article, user): """Return True even if the user is anonymous.""" return True

# Who can create new groups and users from the wiki? WIKI_CAN_ADMIN= is_superuser # Who can change owner and group membership? WIKI_CAN_ASSIGN= is_superuser # Who can change group membership? WIKI_CAN_ASSIGN_OWNER= is_superuser # Who can change read/write access to groups or others? WIKI_CAN_CHANGE_PERMISSIONS= is_superuser # Who can soft-delete an article? WIKI_CAN_DELETE= is_builder # Who can lock an article and permanently delete it? WIKI_CAN_MODERATE= is_superuser # Who can edit articles? (continues on next page)

392 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) WIKI_CAN_WRITE= is_builder # Who can read articles? WIKI_CAN_READ= is_anyone

Here, we have created three functions: one to return True if the user is the superuser, one to return True if the user is a builder, one to return True no matter what (this includes if the user is anonymous, E.G. if it’s not logged-in). We then change settings to allow either the superuser or each builder to moderate, read, write, delete, and more. You can, of course, add more functions, adapting them to your need. This is just a demonstration.

Providing the WIKI_CAN*... settings will bypass the original permission system. The superuser could change permissions of an article, but still, only builders would be able to write it. If you need something more custom, you will have to expand on the functions you use.

Managing wiki pages from Evennia

Unfortunately, Django wiki doesn’t provide a clear and clean entry point to read and write articles from Evennia and it doesn’t seem to be a very high priority. If you really need to keep Django wiki and to create and manage wiki pages from your code, you can do so, but this article won’t elaborate, as this is somewhat more technical. However, it is a good opportunity to present a small project that has been created more recently: evennia-wiki has been created to provide a simple wiki, more tailored to Evennia and easier to connect. It doesn’t, as yet, provide as many options as does Django wiki, but it’s perfectly usable: • Pages have an inherent and much-easier to understand hierarchy based on URLs. • Article permissions are connected to Evennia groups and are much easier to accommodate specific requirements. • Articles can easily be created, read or updated from the Evennia code itself. • Markdown is fully-supported with a default integration to Bootstrap to look good on an Evennia website. Tables and table of contents are supported as well as wiki links. • The process to override wiki templates makes full use of the template_overrides directory. However evennia-wiki doesn’t yet support: • Images in markdown and the uploading schema. If images are important to you, please consider contributing to this new project. • Modifying permissions on a per page/setting basis. • Moving pages to new locations. • Viewing page history. Considering the list of features in Django wiki, obviously other things could be added to the list. However, these features may be the most important and useful. Additional ones might not be that necessary. If you’re interested in supporting this little project, you are more than welcome to contribute to it. Thanks!

13.26. Add a wiki on your website 393 Evennia Documentation, Release 0.9

13.27 Web Character Generation

13.27.1 Introduction

This tutorial will create a simple web-based interface for generating a new in-game Character. Accounts will need to have first logged into the website (with their AccountDB account). Once finishing character generation the Character will be created immediately and the Accounts can then log into the game and play immediately (the Character will not require staff approval or anything like that). This guide does not go over how to create an AccountDB on the website with the right permissions to transfer to their web-created characters. It is probably most useful to set MULTISESSION_MODE = 2 or 3 (which gives you a character-selection screen when you log into the game later). Other modes can be used with some adaptation to auto-puppet the new Character. You should have some familiarity with how Django sets up its Model Template View framework. You need to under- stand what is happening in the basic Web Character View tutorial. If you don’t understand the listed tutorial or have a grasp of Django basics, please look at the Django tutorial to get a taste of what Django does, before throwing Evennia into the mix (Evennia shares its API and attributes with the website interface). This guide will outline the format of the models, views, urls, and html templates needed.

13.27.2 Pictures

Here are some screenshots of the simple app we will be making. Index page, with no character application yet done:

***

Having clicked the “create” link you get to create your character (here we will only have name and background, you can add whatever is needed to fit your game):

***

Back to the index page. Having entered our character application (we called our character “TestApp”) you see it listed:

***

We can also view an already written character application by clicking on it - this brings us to the detail page:

***

394 Chapter 13. Tutorials Evennia Documentation, Release 0.9

13.27.3 Installing an App

Assuming your game is named “mygame”, navigate to your mygame/ directory, and type:

evennia startapp chargen

This will initialize a new Django app we choose to call “chargen”. It is directory containing some basic starting things Django needs. You will need to move this directory: for the time being, it is in your mygame directory. Better to move it in your mygame/web directory, so you have mygame/web/chargen in the end. Next, navigate to mygame/server/conf/settings.py and add or edit the following line to make Evennia (and Django) aware of our new app:

INSTALLED_APPS+=('web.chargen',)

After this, we will get into defining our models (the description of the database storage), views (the server-side website content generators), urls (how the web browser finds the pages) and templates (how the web page should be structured).

Checkpoint:

• you should have a folder named chargen or whatever you chose in your mygame/web/ directory • you should have your application name added to your INSTALLED_APPS in settings.py

13.27.4 Create Models

Models are created in mygame/web/chargen/models.py. A Django database model 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. We need to define what a character application actually is. This will differ from game to game so for this tutorial we will define a simple character sheet with the following database fields: • app_id (AutoField): Primary key for this character application sheet. • char_name (CharField): The new character’s name. • date_applied (DateTimeField): Date that this application was received. • background (TextField): Character story background. • account_id (IntegerField): Which account ID does this application belong to? This is an AccountID from the AccountDB object. • submitted (BooleanField): True/False depending on if the application has been submitted yet. 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:

# in mygame/web/chargen/models.py

from django.db import models

class CharApp(models.Model): app_id= models.AutoField(primary_key= True) char_name= models.CharField(max_length=80, verbose_name='Character Name') (continues on next page)

13.27. Web Character Generation 395 Evennia Documentation, Release 0.9

(continued from previous page) date_applied= models.DateTimeField(verbose_name='Date Applied') background= models.TextField(verbose_name='Background') account_id= models.IntegerField(default=1, verbose_name='Account ID') submitted= models.BooleanField(default= False)

You should consider how you are going to link your application to your account. For this tutorial, we are using the account_id attribute on our character application model in order to keep track of which characters are owned by which accounts. Since the account id is a primary key in Evennia, it is a good candidate, as you will never have two of the same IDs in Evennia. You can feel free to use anything else, but for the purposes of this guide, we are going to use account ID to join the character applications with the proper account.

Checkpoint:

• you should have filled out mygame/web/chargen/models.py with the model class shown above (even- tually adding fields matching what you need for your game).

13.27.5 Create Views

Views are server-side constructs that make dynamic data available to a web page. We are going to add them to mygame/web/chargen.views.py. Each view in our example represents the backbone of a specific web page. We will use three views and three pages here: • The index (managing index.html). This is what you see when you navigate to http://yoursite.com/ chargen. • The detail display sheet (manages detail.html). A page that passively displays the stats of a given Charac- ter. • 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

# file mygame/web/chargen.views.py from.models import CharApp def index(request): current_user= request.user # current user logged in p_id= current_user.id # the account id # submitted Characters by this account sub_apps= CharApp.objects.filter(account_id=p_id, submitted= True) context={'sub_apps': sub_apps} # make the variables in 'context' available to the web page template return render(request,'chargen/index.html', context)

Detail view

Our detail page will have pertinent character application information our users can see. Since this is a basic demon- stration, our detail page will only show two fields:

396 Chapter 13. Tutorials Evennia Documentation, Release 0.9

• Character name • Character background We will use the account ID again just to double-check that whoever tries to check our character page is actually the account who owns the application.

# file mygame/web/chargen.views.py def detail(request, app_id): app= CharApp.objects.get(app_id=app_id) name= app.char_name background= app.background submitted= app.submitted p_id= request.user.id context={'name': name,'background': background, 'p_id': p_id,'submitted': submitted} return render(request,'chargen/detail.html', context)

13.27.6 Creating view

Predictably, our create function will be the most complicated of the views, as it needs to accept information from the user, validate the information, and send the information to the server. Once the form content is validated will actually create a playable Character. The form itself we will define first. In our simple example we are just looking for the Character’s name and back- ground. This form we create in mygame/web/chargen/forms.py:

# file mygame/web/chargen/forms.py from django import forms class AppForm(forms.Form): name= forms.CharField(label='Character Name', max_length=80) background= forms.CharField(label='Background')

Now we make use of this form in our view.

# file mygame/web/chargen/views.py from web.chargen.models import CharApp from web.chargen.forms import AppForm from django.http import HttpResponseRedirect from datetime import datetime from evennia.objects.models import ObjectDB from django.conf import settings from evennia.utils import create def creating(request): user = request.user if request.method == 'POST': form = AppForm(request.POST) if form.is_valid(): name = form.cleaned_data['name'] background = form.cleaned_data['background'] applied_date = datetime.now() submitted = True (continues on next page)

13.27. Web Character Generation 397 Evennia Documentation, Release 0.9

(continued from previous page) if 'save' in request.POST: submitted = False app = CharApp(char_name=name, background=background, date_applied=applied_date, account_id=user.id, submitted=submitted) app.save() if submitted: # Create the actual character object typeclass = settings.BASE_CHARACTER_TYPECLASS home = ObjectDB.objects.get_id(settings.GUEST_HOME) # turn the permissionhandler to a string perms = str(user.permissions) # create the character 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 # puppet it char.locks.add("puppet:id(%i) or pid(%i) or perm(Developers) " "or pperm(Developers)" % (char.id, user.id)) char.db.background = background # set the character background return HttpResponseRedirect('/chargen') else: form = AppForm() return render(request, 'chargen/create.html', {'form': form})

Note also that we basically create the character using the Evennia API, and we grab the proper permissions from the ``AccountDB`` object and copy them to the character object. We take the user permissions attribute and turn that list of strings into a string object in order for the create\_object function to properly process the permissions.

Most importantly, the following attributes must be set on the created character object: • Evennia permissions (copied from the AccountDB). • The right puppet locks so the Account can actually play as this Character later. • The relevant Character typeclass • Character name (key) • The Character’s home room location (#2 by default) Other attributes are strictly speaking optional, such as the background attribute on our character. It may be a good idea to decompose this function and create a separate _create_character function in order to set up your character object the account owns. But with the Evennia API, setting custom attributes is as easy as doing it in the meat of your Evennia game directory. After all of this, our views.py file should look like something like this:

# file mygame/web/chargen/views.py from django.shortcuts import render from web.chargen.models import CharApp from web.chargen.forms import AppForm from django.http import HttpResponseRedirect from datetime import datetime (continues on next page)

398 Chapter 13. Tutorials Evennia Documentation, Release 0.9

(continued from previous page) from evennia.objects.models import ObjectDB from django.conf import settings from evennia.utils import create def index(request): current_user= request.user # current user logged in p_id= current_user.id # the account id # submitted apps under this account sub_apps= CharApp.objects.filter(account_id=p_id, submitted= True) context={'sub_apps': sub_apps} return render(request,'chargen/index.html', context) def detail(request, app_id): app= CharApp.objects.get(app_id=app_id) name= app.char_name background= app.background submitted= app.submitted p_id= request.user.id context={'name': name,'background': background, 'p_id': p_id,'submitted': submitted} return render(request,'chargen/detail.html', context) def creating(request): user= request.user if request.method =='POST': form= AppForm(request.POST) if form.is_valid(): name= form.cleaned_data['name'] background= form.cleaned_data['background'] applied_date= datetime.now() submitted= True if 'save' in request.POST: submitted= False app= CharApp(char_name=name, background=background, date_applied=applied_date, account_id=user.id, submitted=submitted) app.save() if submitted: # Create the actual character object typeclass= settings.BASE_CHARACTER_TYPECLASS home= ObjectDB.objects.get_id(settings.GUEST_HOME) # turn the permissionhandler to a string perms= str(user.permissions) # create the character 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 # puppet it char.locks.add("puppet:id(%i) or pid(%i) or perm(Developers)" "or pperm(Developers)"% (char.id, user.id)) char.db.background= background # set the character background return HttpResponseRedirect('/chargen') else: form= AppForm() return render(request,'chargen/create.html',{'form': form})

13.27. Web Character Generation 399 Evennia Documentation, Release 0.9

Checkpoint:

• you’ve defined a views.py that has an index, detail, and creating functions. • you’ve defined a forms.py with the AppForm class needed by the creating function of views.py. • your mygame/web/chargen directory should now have a views.py and forms.py file

13.27.7 Create URLs

URL patterns helps redirect requests from the web browser to the right views. These patterns are created in mygame/ web/chargen/urls.py.

# file mygame/web/chargen/urls.py from django.conf.urls import url from web.chargen import views urlpatterns=[ # ex: /chargen/ url(r'^$', views.index, name='index'), # ex: /chargen/5/ url(r'^(?P[0-9]+)/$', views.detail, name='detail'), # ex: /chargen/create url(r'^create/$', views.creating, name='creating'), ]

You could change the format as you desire. To make it more secure, you could remove app_id from the “detail” url, and instead just fetch the account’s applications using a unifying field like account_id to find all the character application objects to display. We must also update the main mygame/web/urls.py file (that is, one level up from our chargen app), so the main website knows where our app’s views are located. Find the patterns variable, and change it to include:

# in file mygame/web/urls.py from django.conf.urls import url, include

# default evennia patterns from evennia.web.urls import urlpatterns

# eventual custom patterns custom_patterns=[ # url(r'/desired/url/', view, name='example'), ]

# this is required by Django. urlpatterns+=[ url(r'^chargen/', include('web.chargen.urls')), ] urlpatterns= custom_patterns+ urlpatterns

Checkpoint:

• You’ve created a urls.py file in the mygame/web/chargen directory

400 Chapter 13. Tutorials Evennia Documentation, Release 0.9

• You have edited the main mygame/web/urls.py file to include urls to the chargen directory.

13.27.8 HTML Templates

So we have our url patterns, views, and models defined. Now we must define our HTML templates that the actual user will see and interact with. For this tutorial we us the basic prosimii template that comes with Evennia. Take note that we use user.is_authenticated to make sure that the user cannot create a character without logging in. These files will all go into the /mygame/web/chargen/templates/chargen/ directory. index.html

This HTML template should hold a list of all the applications the account currently has active. For this demonstra- tion, we will only list the applications that the account has submitted. You could easily adjust this to include saved applications, or other types of applications if you have different kinds. Please refer back to views.py to see where we define the variables these templates make use of.

{% extends "base.html" %} {% block content %} {% if user.is_authenticated %}

Character Generation

{% if sub_apps %} {% else %}

You haven't submitted any character applications.

{% endif %} {% else %}

Please loginfirst.

{% endif %} {% endblock %} detail.html

This page should show a detailed character sheet of their application. This will only show their name and character background. You will likely want to extend this to show many more fields for your game. In a full-fledged character generation, you may want to extend the boolean attribute of submitted to allow accounts to save character applications and submit them later.

{% extends "base.html" %} {% block content %}

Character Information

{% if user.is_authenticated %} (continues on next page)

13.27. Web Character Generation 401 Evennia Documentation, Release 0.9

(continued from previous page) {% if user.id == p_id %}

{{name}}

Background

{{background}}

Submitted: {{submitted}}

{% else %}

You didn't submit this character.

{% endif %} {% else %}

You aren't logged in.

{% endif %} {% endblock %} create.html

Our create HTML template will use the Django form we defined back in views.py/forms.py to drive the majority of the application process. There will be a form input for every field we defined in forms.py, which is handy. We have used POST as our method because we are sending information to the server that will update the database. As an alternative, GET would be much less secure. You can read up on documentation elsewhere on the web for GET vs. POST.

{% extends "base.html" %} {% block content %}

Character Creation

{% if user.is_authenticated %}
{% csrf_token %} {{ form }}
{% else %}

You aren't logged in.

{% endif %} {% endblock %}

Checkpoint:

• Create a index.html, detail.html and create.html template in your mygame/web/chargen/ templates/chargen directory

13.27.9 Activating your new character generation

After finishing this tutorial you should have edited or created the following files: mygame/web/urls.py mygame/web/chargen/models.py mygame/web/chargen/views.py mygame/web/chargen/urls.py mygame/web/chargen/templates/chargen/index.html mygame/web/chargen/templates/chargen/create.html mygame/web/chargen/templates/chargen/detail.html

402 Chapter 13. Tutorials Evennia Documentation, Release 0.9

Once you have all these files stand in your mygame/folder and run:

evennia makemigrations evennia migrate

This will create and update the models. If you see any errors at this stage, read the traceback carefully, it should be relatively easy to figure out where the error is. Login to the website (you need to have previously registered an Player account with the game to do this). Next you navigate to http://yourwebsite.com/chargen (if you are running locally this will be something like http://localhost:4001/chargen and you will see your new app in action. This should hopefully give you a good starting point in figuring out how you’d like to approach your own web gener- ation. The main difficulties are in setting the appropriate settings on your newly created character object. Thankfully, the Evennia API makes this easy.

13.27.10 Adding a no CAPCHA reCAPCHA on your character generation

As sad as it is, if your server is open to the web, bots might come to visit and take advantage of your open form to create hundreds, thousands, millions of characters if you give them the opportunity. This section shows you how to use the No CAPCHA reCAPCHA designed by Google. Not only is it easy to use, it is user-friendly. . . for humans. A simple checkbox to check, except if Google has some suspicion, in which case you will have a more difficult test with an image and the usual text inside. It’s worth pointing out that, as long as Google doesn’t suspect you of being a robot, this is quite useful, not only for common users, but to screen-reader users, to which reading inside of an image is pretty difficult, if not impossible. And to top it all, it will be so easy to add in your website.

Step 1: Obtain a SiteKey and secret from Google

The first thing is to ask Google for a way to safely authenticate your website to their service. To do it, we need to create a site key and a secret. Go to https://www.google.com/recaptcha/admin to create such a site key. It’s quite easy when you have a Google account. When you have created your site key, save it safely. Also copy your secret key as well. You should find both informa- tion on the web page. Both would contain a lot of letters and figures.

Step 2: installing and configuring the dedicated Django app

Since Evennia runs on Django, the easiest way to add our CAPCHA and perform the proper check is to install the dedicated Django app. Quite easy:

pip install django-nocaptcha-recaptcha

And add it to the installed apps in your settings. In your mygame/server/conf/settings.py, you might have something like this:

# ... INSTALLED_APPS+=( 'web.chargen', 'nocaptcha_recaptcha', )

Don’t close the setting file just yet. We have to add in the site key and secret key. You can add them below:

13.27. Web Character Generation 403 Evennia Documentation, Release 0.9

# NoReCAPCHA site key NORECAPTCHA_SITE_KEY="PASTE YOUR SITE KEY HERE" # NoReCAPCHA secret key NORECAPTCHA_SECRET_KEY="PUT YOUR SECRET KEY HERE"

Step 3: Adding the CAPCHA to our form

Finally we have to add the CAPCHA to our form. It will be pretty easy too. First, open your web/chargen/ forms.py file. We’re going to add a new field, but hopefully, all the hard work has been done for us. Update at your convenience, You might end up with something like this: from django import forms from nocaptcha_recaptcha.fields import NoReCaptchaField class AppForm(forms.Form): name= forms.CharField(label='Character Name', max_length=80) background= forms.CharField(label='Background') captcha= NoReCaptchaField()

As you see, we added a line of import (line 2) and a field in our form. And lastly, we need to update our HTML file to add in the Google library. You can open web/chargen/ templates/chargen/create.html. There’s only one line to add:

And you should put it at the bottom of the page. Just before the closing body would be good, but for the time being, the base page doesn’t provide a footer block, so we’ll put it in the content block. Note that it’s not the best place, but it will work. In the end, your web/chargen/templates/chargen/create.html file should look like this:

{% extends"base.html"%} {% block content%}

Character Creation

{% if user.is_authenticated%}
{% csrf_token%} {{ form }}
{% else %}

You aren't logged in.

{% endif%} {% endblock%}

Reload and open http://localhost:4001/chargen/create and you should see your beautiful CAPCHA just before the “submit” button. Try not to check the checkbox to see what happens. And do the same while checking the checkbox!

13.28 Bootstrap & Evennia

# What is Bootstrap? Evennia’s new default web page uses a framework called Bootstrap. This framework is in use across the internet - you’ll probably start to recognize its influence once you learn some of the common design patterns. This switch is great for web developers, perhaps like yourself, because instead of wondering about setting up different grid systems

404 Chapter 13. Tutorials Evennia Documentation, Release 0.9 or what custom class another designer used, we have a base, a bootstrap, to work from. Bootstrap is responsive by default, and comes with some default styles that Evennia has lightly overrode to keep some of the same colors and styles you’re used to from the previous design.

For your reading pleasure, a brief overview of Bootstrap follows. For more in-depth info, please read the documentation. ***

13.28.1 The Layout System

Other than the basic styling Bootstrap includes, it also includes a built in layout and grid system. The first part of this system is the container.

The container is meant to hold all your page content. Bootstrap provides two types: fixed-width and full-width. Fixed-width containers take up a certain max-width of the page - they’re useful for limiting the width on Desktop or Tablet platforms, instead of making the content span the width of the page.

Full width containers take up the maximum width available to them - they’ll span across a wide-screen desktop or a smaller screen phone, edge-to-edge.

The second part of the layout system is the grid. This is the bread-and-butter of the layout of Bootstrap - it allows you to change the size of elements depending on the size of the screen, without writing any media queries. We’ll briefly go over it - to learn more, please read the docs or look at the source code for Evennia’s home page in your browser. Important! Grid elements should be in a .container or .container-fluid. This will center the contents of your site. Bootstrap’s grid system allows you to create rows and columns by applying classes based on breakpoints. The default breakpoints are extra small, small, medium, large, and extra-large. If you’d like to know more about these breakpoints, please take a look at the documentation for them. To use the grid system, first create a container for your content, then add your rows and columns like so:

1 of3
2 of3
3 of3 (continues on next page)

13.28. Bootstrap & Evennia 405 Evennia Documentation, Release 0.9

(continued from previous page)

This layout would create three equal-width columns. To specify your sizes - for instance, Evennia’s default site has three columns on desktop and tablet, but reflows to single-column on smaller screens. Try it out!

1 of4
2 of4
3 of4
4 of4

This layout would be 4 columns on large screens, 2 columns on medium screens, and 1 column on anything smaller.

To learn more about Bootstrap’s grid, please take a look at the docs ***

13.28.2 More Bootstrap

Bootstrap also provides a huge amount of utilities, as well as styling and content elements. To learn more about them, please read the Bootstrap docs or read one of our other web tutorials.

13.29 Bootstrap Components and Utilities

Bootstrap provides many utilities and components you can use when customizing Evennia’s web presence. We’ll go over a few examples here that you might find useful. Please take a look at either the basic web tutorial or the web character view tutorial to get a feel for how to add pages to Evennia’s website to test these examples.

13.29.1 General Styling

Bootstrap provides base styles for your site. These can be customized through CSS, but the default styles are intended to provide a consistent, clean look for sites.

406 Chapter 13. Tutorials Evennia Documentation, Release 0.9

Color

Most elements can be styled with default colors. Take a look at the documentation to learn more about these colors - suffice to say, adding a class of text-* or bg-*, for instance, text-primary, sets the text color or background color.

Borders

Simply adding a class of ‘border’ to an element adds a border to the element. For more in-depth info, please read the documentation on borders..

You can also easily round corners just by adding a class.

Spacing

Bootstrap provides classes to easily add responsive margin and padding. Most of the time, you might like to add margins or padding through CSS itself - however these classes are used in the default Evennia site. Take a look at the docs to learn more.

13.29.2 Components

Buttons

Buttons in Bootstrap are very easy to use - button styling can be added to

Cards

Cards provide a container for other elements that stands out from the rest of the page. The “Accounts”, “Recently Connected”, and “Database Stats” on the default webpage are all in cards. Cards provide quite a bit of formatting options - the following is a simple example, but read the documentation or look at the site’s source for more.

Card title

Card subtitle

Fancy, isn't it?

Card link

13.29. Bootstrap Components and Utilities 407 Evennia Documentation, Release 0.9

Jumbotron

Jumbotrons are useful for featuring an image or tagline for your game. They can flow with the rest of your content or take up the full width of the page - Evennia’s base site uses the former.

Full Width Jumbotron

Look at the source of the default Evennia page for a regular

˓→Jumbotron

Forms

Forms are highly customizable with Bootstrap. For a more in-depth look at how to use forms and their styles in your own Evennia site, please read over the web character gen tutorial.

13.30 Evennia for Diku Users

Evennia represents a learning curve for those who used to code on Diku type MUDs. While coding in Python is easy if you already know C, the main effort is to get rid of old C programming habits. Trying to code Python the way you code C will not only look ugly, it will lead to less optimal and harder to maintain code. Reading Evennia example code is a good way to get a feel for how different problems are approached in Python. Overall, Python offers an extensive library of resources, safe memory management and excellent handling of errors. While Python code does not run as fast as raw C code does, the difference is not all that important for a text-based game. The main advantage of Python is an extremely fast development cycle with and easy ways to create game systems that would take many times more code and be much harder to make stable and maintainable in C.

13.30.1 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 modified and extended by the server loading Python modules at run-time. It also runs on all computer platforms Python runs on (which is basically everywhere). • Vanilla Diku type engines save their data in custom flat file type storage solutions. By contrast, Evennia stores all game data in one of several supported SQL databases. Whereas flat files have the advantage of being easier to implement, they (normally) lack many expected safety features and ways to effectively extract subsets of the stored data. For example, if the server loses power while writing to a flatfile it may become corrupt and the data lost. A proper database solution is 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.

13.30.2 Some Familiar Things

Diku expresses the character object referenced normally by: struct char ch* then all character-related fields can be accessed by ch->. In Evennia, one must pay attention to what object you are using, and when you are accessing another through back-handling, that you are accessing the right object. In Diku C, accessing character object is normally done by:

408 Chapter 13. Tutorials Evennia Documentation, Release 0.9

/* creating pointer of both character and room struct */ void(struct char ch*, struct room room*){ int dam; if (ROOM_FLAGGED(room, ROOM_LAVA)){ dam= 100 ch->damage_taken= dam }; };

As an example for creating Commands in Evennia via the from evennia import Command the character ob- ject that calls the command is denoted by a class property as self.caller. In this example self.caller is essentially the ‘object’ that has called the Command, but most of the time it is an Account object. For a more familiar Diku feel, create a variable that becomes the account object as:

#mygame/commands/command.py from evennia import Command class CmdMyCmd(Command): """ This is a Command Evennia Object """

[...]

def func(self): ch= self.caller # then you can access the account object directly by using the familiar ch. ch.msg("...") account_name= ch.name race= ch.db.race

As mentioned above, care must be taken what specific object you are working with. If focused on a room object and you need to access the account object:

#mygame/typeclasses/room.py from evennia import DefaultRoom class MyRoom(DefaultRoom): [...]

def is_account_object(self, object): # a test to see if object is an account [...]

def myMethod(self): #self.caller would not make any sense, since self refers to the # object of 'DefaultRoom', you must find the character obj first: for ch in self.contents: if self.is_account_object(ch): # now you can access the account object with ch: account_name= ch.name race= ch.db.race

13.30. Evennia for Diku Users 409 Evennia Documentation, Release 0.9

Emulating Evennia to Look and Feel Like A Diku/ROM

To emulate a Diku Mud on Evennia some work has to be done before hand. If there is anything that all coders and builders remember from Diku/Rom days is the presence of VNUMs. Essentially all data was saved in flat files and indexed by VNUMs for easy access. Evennia has the ability to emulate VNUMS to the extent of categorising rooms/mobs/objs/trigger/zones[. . . ] into vnum ranges. Evennia has objects that are called Scripts. As defined, they are the ‘out of game’ instances that exist within the mud, but never directly interacted with. Scripts can be used for timers, mob AI, and even a stand alone databases. Because of their wonderful structure all mob, room, zone, triggers, etc.. data can be saved in independently created global scripts. Here is a sample mob file from a Diku Derived flat file.

#0 mob0~ mob0~ mob0 ~ Mob0 ~ 10 0 0 0 0 0 0 0 0 E 1 20 9 0d0+10 1d2+0 10 100 8 8 0 E #1 Puff dragon fractal~ 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? ~ 516106 0 0 0 2128 0 0 0 1000 E 34 9 -10 6d6+340 5d5+5 340 115600 8 8 2 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. First, let’s create a global script that does absolutely nothing and isn’t attached to anything. You can either create this directly in-game with the @py command or create it in another file to do some checks and balances if for whatever reason the script needs to be created again. Progmatically it can be done like so: from evennia import create_script mob_db= create_script("typeclasses.scripts.DefaultScript", key="mobdb", persistent=True, obj=None) mob_db.db.vnums={}

Just by creating a simple script object and assigning it a ‘vnums’ attribute as a type dictionary. Next we have to create the mob layout..

410 Chapter 13. Tutorials Evennia Documentation, Release 0.9

# vnum : mob_data

mob_vnum_1={ 'key':'puff', 'sdesc':'puff the fractal dragon', 'ldesc':'Puff the Fractal Dragon is here,'\ 'contemplating a higher reality.', 'ddesc':' Is that some type of differential curve'\ 'involving some strange, and unknown calculus'\ 'that she seems to be made out of?', [...] }

# Then saving it to the data, assuming you have the script obj stored in a variable. mob_db.db.vnums[1]= mob_vnum_1

This is a very ‘caveman’ example, but it gets the idea across. You can use the keys in the mob_db.vnums to act as the mob vnum while the rest contains the data.. Much simpler to read and edit. If you plan on taking this route, you must keep in mind that by default evennia ‘looks’ at different properties when using the look command for instance. If you create an instance of this mob and make its self.key = 1, by default evennia will say Here is : 1 You must restructure all default commands so that the mud looks at different properties defined on your mob.

13.31 Evennia for MUSH Users

This page is adopted from an article originally posted for the MUSH community ‘here on musoapbox.net‘_. MUSHes are text multiplayer games traditionally used for heavily roleplay-focused game styles. They are often (but not always) utilizing game masters and human oversight over code automation. MUSHes are traditionally built on the TinyMUSH-family of game servers, like PennMUSH, TinyMUSH, TinyMUX and RhostMUSH. Also their siblings MUCK and MOO are often mentioned together with MUSH since they all inherit from the same TinyMUD base. A major feature is the ability to modify and program the game world from inside the game by using a custom scripting language. We will refer to this online scripting as softcode here. Evennia works quite differently from a MUSH both in its overall design and under the hood. The same things are achievable, just in a different way. Here are some fundamental differences to keep in mind if you are coming from the MUSH world.

13.31.1 Developers vs Players

In MUSH, users tend to code and expand all aspects of the game from inside it using softcode. A MUSH can thus be said to be managed solely by Players with different levels of access. Evennia on the other hand, differentiates between the role of the Player and the Developer. • An Evennia Developer works in Python from outside the game, in what MUSH would consider “hardcode”. Developers implement larger-scale code changes and can fundamentally change how the game works. They then load their changes into the running Evennia server. Such changes will usually not drop any connected players. • An Evennia Player operates from inside the game. Some staff-level players are likely to double as developers. Depending on access level, players can modify and expand the game’s world by digging new rooms, creating new objects, alias commands, customize their experience and so on. Trusted staff may get access to Python via

13.31. Evennia for MUSH Users 411 Evennia Documentation, Release 0.9

the @py command, but this would be a security risk for normal Players to use. So the Player usually operates by making use of the tools prepared for them by the Developer - tools that can be as rigid or flexible as the developer desires.

13.31.2 Collaborating on a game - Python vs Softcode

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 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. For Developers of the game, the difference is larger: Code is mainly written outside the game in Python modules rather than in-game on the command line. Python is a very popular and well-supported language with tons of documentation and help to be found. The Python standard library is also a great help for not having to reinvent the wheel. But that said, while Python is considered one of the easier languages to learn and use it is undoubtedly very different from MUSH softcode. While softcode allows collaboration in-game, Evennia’s external coding instead opens up the possibility for collabora- tion using professional version control tools and bug tracking using websites like github (or bitbucket for a free private repo). Source code can be written in proper text editors and IDEs with refactoring, syntax highlighting and all other conveniences. In short, collaborative development of an Evennia game is done in the same way most professional collaborative development is done in the world, meaning all the best tools can be used.

13.31.3 @parent vs @typeclass and @spawn

Inheritance works differently in Python than in softcode. Evennia has no concept of a “master object” that other objects inherit from. There is in fact no reason at all to introduce “virtual objects” in the game world - code and data are kept separate from one another. In Python (which is an object oriented language) one instead creates classes - these are like blueprints from which you spawn any number of object instances. Evennia also adds the extra feature that every instance is persistent in the database (this means no SQL is ever needed). To take one example, a unique character in Evennia is an instances of the class Character. One parallel to MUSH’s @parent command may be Evennia’s @typeclass command, which changes which class an already existing object is an instance of. This way you can literally turn a Character into a Flowerpot on the spot. if you are new to object oriented design it’s important to note that all object instances of a class does not have to be identical. If they did, all Characters would be named the same. Evennia allows to customize individual objects in many different ways. One way is through Attributes, which are database-bound properties that can be linked to any object. For example, you could have an Orc class that defines all the stuff an Orc should be able to do (probably in turn inheriting from some Monster class shared by all monsters). Setting different Attributes on different instances (different strength, equipment, looks etc) would make each Orc unique despite all sharing the same class. The @spawn command allows one to conveniently choose between different “sets” of Attributes to put on each new Orc (like the “warrior” set or “shaman” set) . Such sets can even inherit one another which is again somewhat remniscent at least of the effect of @parent and the object-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.

412 Chapter 13. Tutorials Evennia Documentation, Release 0.9

13.31.4 A first step making things more familiar

We will here give two examples of customizing Evennia to be more familiar to a MUSH Player.

Activating a multi-descer

By default Evennia’s desc command updates your description and that’s it. There is a more feature-rich optional “multi-descer” in evennia/contrib/multidesc.py though. This alternative allows for managing and com- bining a multitude of keyed descriptions. To activate the multi-descer, cd to your game folder and into the commands sub-folder. There you’ll find the file default_cmdsets.py. In Python lingo all *.py files are called modules. Open the module in a text editor. We won’t go into Evennia in-game Commands and Command sets further here, but suffice to say Evennia allows you to change which commands (or versions of commands) are available to the player from moment to moment depending on circumstance. Add two new lines to the module as seen below:

# the file mygame/commands/default_cmdsets.py # [...]

from evennia.contrib import multidescer # <- added now

class CharacterCmdSet(default_cmds.CharacterCmdSet): """ The CharacterCmdSet contains general in-game commands like look, get etc available on in-game Character objects. It is merged with the AccountCmdSet when an Account puppets a Character. """ key="DefaultCharacter"

def at_cmdset_creation(self): """ Populates the cmdset """ super().at_cmdset_creation() # # any commands you add below will overload the default ones. # self.add(multidescer.CmdMultiDesc()) # <- added now # [...]

Note that Python cares about indentation, so make sure to indent with the same number of spaces as shown above! So what happens above? We import the module evennia/contrib/multidescer.py at the top. Once imported we can access stuff inside that module using (.). The multidescer is defined as a class CmdMultiDesc (we could find this out by opening said module in a text editor). At the bottom we create a new instance of this class and add it to the CharacterCmdSet class. For the sake of this tutorial we only need to know that CharacterCmdSet contains all commands that should be be available to the Character by default. This whole thing will be triggered when the command set is first created, which happens on server start. So we need to reload Evennia with @reload - no one will be disconnected by doing this. If all went well you should now be able to use desc (or +desc) and find that you have more possibilities:

> help+desc # get help on the command >+desc eyes= His eyes are blue. >+desc basic= A big guy. (continues on next page)

13.31. Evennia for MUSH Users 413 Evennia Documentation, Release 0.9

(continued from previous page) >+desc/set basic++ eyes # we add an extra space between > look me A big guy. His eyes are blue.

If there are errors, a traceback will show in the server log - several lines of text showing where the error occurred. Find where the error is by locating the line number related to the default_cmdsets.py file (it’s the only one you’ve changed so far). Most likely you mis-spelled something or missed the indentation. Fix it and either @reload again or run evennia start as needed.

Customizing the multidescer syntax

As seen above the multidescer uses syntax like this (where |/ are Evennia’s tags for line breaks) :

>+desc/set basic+|/|/+ cape+ footwear+|/|/+ attitude

This use of + was prescribed by the Developer that coded this +desc command. What if the Player doesn’t like this syntax though? Do players need to pester the dev to change it? Not necessarily. While Evennia does not allow the player to build their own multi-descer on the command line, it does allow for re-mapping the command syntax to one they prefer. This is done using the nick command. Here’s a nick that changes how to input the command above:

> nick setdesc $1 $2 $3 $4 = +desc/set $1 + |/|/ + $2 + $3 + |/|/ + $4

The string on the left will be matched against your input and if matching, it will be replaced with the string on the right. The $-type tags will store space-separated arguments and put them into the replacement. The nick allows shell-like wildcards, so you can use *, ?, [...], [!...] etc to match parts of the input. The same description as before can now be set as

> setdesc basic cape footwear attitude

With the nick functionality players can mitigate a lot of syntax dislikes even without the developer changing the underlying Python code.

13.31.5 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. 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 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. You may also find it useful to shop through the evennia/contrib/ folder. The Tutorial world 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 to try out. If you feel you want a more visual overview you can also look at Evennia in pictures. . . . And of course, if you need further help you can always drop into the Evennia chatroom or post a question in our forum/mailing list!

414 Chapter 13. Tutorials CHAPTER 14

Appendix

This chapter groups various extra resources and pieces of information.

14.1 Links

A list of resources that may be useful for Evennia users and developers.

14.1.1 Official Evennia links

• evennia.com - Main Evennia portal page. Links to all corners of Evennia. • Evennia github page - Download code and read documentation. • Evennia official chat channel - Our official IRC chat #evennia at irc.freenode.net:6667. • Evennia forums/mailing list - Web interface to our google group. • Evennia development blog - Musings from the lead developer. • Evennia’s manual on ReadTheDocs - Read and download offline in html, PDF or epub formats.

• ‘Evennia Game Index‘_ - An automated listing of Evennia games.

• Evennia on Open Hub • Evennia on OpenHatch • Evennia on PyPi • Evennia subreddit (not much there yet though)

415 Evennia Documentation, Release 0.9

14.1.2 Third-party Evennia utilities and resources

For publicly available games running on Evennia, add and find those in the ‘Evennia game index‘_ instead! • Discord Evennia channel - This is a fan-driven Discord channel with a bridge to the official Evennia IRC channel.

• Discord live blog of the Blackbirds Evennia game project. • Unreal Engine Evennia plugin - an in-progress Unreal plugin for integrating Evennia with Epic Games’ Unreal Engine. • The dark net/March Hare MUD from the 2019 DEF CON 27 hacker conference in Paris. This is an Evennia game dir with batchcode to build the custom Hackers style cyberspace zone with puzzles and challenges used during the conference. • Arx sources - Open-source code release of the very popular Arx Evennia game. Here are instructions for installing • Evennia-wiki - An Evennia-specific Wiki for your website. • Evcolor - Optional coloration for Evennia unit-test output. • Paxboards - Evennia bulletin board system (both for telnet/web). • Encarnia sources - An open-sourced game dir for Evennia with things like races, combat etc. Summary here. • The world of Cool battles sources - Open source turn-based battle system for Evennia. It also has a live demo. • nextRPI - A github project for making a toolbox for people to make RPI-style Evennia games. • Muddery - A mud framework under development, based on an older fork of Evennia. It has some specific design goals for building and extending the game based on input files. • vim-evennia - A mode for editing batch-build files (.ev) files in the vim text editor (Emacs users can use evennia-mode.el).

• Other Evennia-related repos on github

• EvCast video series - Tutorial videos explaining installing Evennia, basic Python etc. • Evennia-docker - Evennia in a Docker container for quick install and deployment in just a few commands. • Evennia’s docs in Chinese - A translated mirror of a slightly older Evennia version. Announcement here. • Evennia for MUSHers - An article describing Evennia for those used to the MUSH way of doing things. • ‘Language Understanding for Text games using Deep reinforcement learning‘_ (PDF) - MIT research paper using Evennia to train AIs.

14.1.3 Other useful mud development resources

• ROM area reader - Parser for converting ROM area files to Python objects. • Gossip MUD chat network

416 Chapter 14. Appendix Evennia Documentation, Release 0.9

14.1.4 General MUD forums and discussions

• MUD Coder’s Guild - A blog and associated Slack channel with discussions on MUD development. • MuSoapbox - Very active Mu* game community mainly focused on MUSH-type gaming. • Imaginary Realities - An e-magazine on game and MUD design that has several articles about Evennia. There is also an archive of older issues from 1998-2001 that are still very relevant. • Optional Realities - Mud development discussion forums that has regular articles on MUD development focused on roleplay-intensive games. After a HD crash it’s not as content-rich as it once was. • MudLab - Mud design discussion forum • MudConnector - Mud listing and forums • MudBytes - Mud listing and forums • Top Mud Sites - Mud listing and forums • Planet Mud-Dev - A blog aggregator following blogs of current MUD development (including Evennia) around the ’net. Worth to put among your RSS subscriptions. • Mud Dev mailing list archive (mirror) - Influential mailing list active 1996-2004. Advanced game design dis- cussions. • Mud-dev wiki - A (very) slowly growing resource on MUD creation. • Mud Client/Server Interaction - A page on classic MUD telnet protocols. • Mud Tech’s fun/cool but . . . - Greg Taylor gives good advice on mud design. • Lost Library of MOO - Archive of scientific articles on mudding (in particular ). • Nick Gammon’s hints thread - Contains a very useful list of things to think about when starting your new MUD. • Lost Garden - A game development blog with long and interesting articles (not MUD-specific) • What Games Are - A blog about general game design (not MUD-specific) • The Alexandrian - A blog about tabletop roleplaying and board games, but with lots of general discussion about rule systems and that could be applicable also for MUDs. • ’s laws of game design - thought-provoking guidelines and things to think about when designing a virtual multiplayer world (Raph is known for among other things).

14.1.5 Literature

(amazon page) - Essential reading for the design of any persistent game world, written by the co-creator of the original game MUD. Published in 2003 but it’s still as relevant now as when it came out. Covers everything you need to know and then some. • Zed A. Shaw Learn Python the Hard way (homepage) - Despite the imposing name this book is for the absolute Python/programming beginner. One learns the language by gradually creating a small text game! It has been used by multiple users before moving on to Evennia. Update: This used to be free to read online, this is no longer the case. • David M. Beazley Python Essential Reference (4th ed) (amazon page) - Our recommended book on Python; it not only efficiently summarizes the language but is also an excellent reference to the standard library for more experienced Python coders. • Luciano Ramalho, Fluent Python (o’reilly page) - This is an excellent book for experienced Python coders willing to take their code to the next level. A great read with a lot of useful info also for veteran Pythonistas.

14.1. Links 417 Evennia Documentation, Release 0.9

• Richard Cantillon An Essay on Economic Theory (free ) - A very good English translation of Essai sur la Nature du Commerce en Général, one of the foundations of modern economic theory. Written in 1730 but the translation is annotated and the essay is actually very easy to follow also for a modern reader. Required reading if you think of implementing a sane game economic system.

14.1.6 Frameworks

• Django’s homepage • Documentation • Code • Twisted homepage • Documentation • Code

14.1.7 Tools

• GIT • Documentation • Learn GIT in 15 minutes (interactive tutorial)

14.1.8 Python Info

• Python Website • Documentation • Tutorial • Library Reference • Language Reference • Python tips and tricks

14.1.9 Credits

• Wiki Home Icons made by Freepik from flaticon.com, licensed under Creative Commons BY 3.0.

14.2 Default Command Help

‘‘_ This page is auto-generated. Do not modify - your changes will be lost. Report problems to the ‘issue tracker‘_.

The full set of default Evennia commands currently contains 92 commands in 9 source files. Our policy for adding default commands is outlined here. More

418 Chapter 14. Appendix Evennia Documentation, Release 0.9 information about how commands work can be found in the documentation for Commands.

14.2.1 A-Z

• ‘‘‘__unloggedin_look_command‘‘‘_ - look when in unlogged-in state • about - show Evennia info • access - show your current game access • addcom - add a channel alias and/or subscribe to a channel • alias - adding permanent aliases for object • allcom - perform admin operations on all channels • ban - ban an account from the server • batchcode - build from batch-code file • batchcommands - build from batch-command file • boot - kick an account from the server. • cboot - kick an account from a channel you control • ccreate - create a new channel • cdesc - describe a channel you control • cdestroy - destroy a channel you created • cemit - send an admin message to a channel you control • channels - list all channels available to you • charcreate - create a new character • chardelete - delete a character - this cannot be undone! • clock - change channel locks of a channel you control • cmdsets - list command sets defined on an object • color - testing which colors your client support • command - This is a parent class for some of the defining objmanip commands • connect - connect to the game • copy - copy an object and its properties • cpattr - copy attributes between objects • create - create a new account account • create - create new objects • cwho - show who is listening to a channel • delcom - remove a channel alias and/or unsubscribe from channel • desc - describe an object or the current room. • destroy - permanently delete objects • dig - build new rooms and connect them to the current location • drop - drop something

14.2. Default Command Help 419 Evennia Documentation, Release 0.9

• emit - admin command for emitting message to multiple objects • examine - get detailed information about an object • find - search the database for objects • force - forces an object to execute a command • get - pick up something • give - give away something to someone • help - get help when in unconnected-in state • help - View help or a list of topics • home - move to your character’s home location • ic - control an object you have permission to puppet • inventory - view inventory • irc2chan - Link an evennia channel to an external IRC channel • link - link existing rooms together with exits • lock - assign a lock definition to an object • look - look at location or object • look - look while out-of-character • mvattr - move attributes between objects • name - change the name and/or aliases of an object • nick - define a personal alias/nick by defining a string to • objects - statistics on objects in the database • ooc - stop puppeting and go ooc • open - open a new exit from the current room • option - Set an account option • page - send a private message to another account • password - change your password • perm - set the permissions of an account/object • pose - strike a pose • py - execute a snippet of python code • quell - use character’s permissions instead of account’s • quit - quit when in unlogged-in state • quit - quit the game • reload - reload the server • reset - reset and reboot the server • rss2chan - link an evennia channel to an external RSS feed • say - speak as your character • script - attach a script to an object

420 Chapter 14. Appendix Evennia Documentation, Release 0.9

• scripts - list and manage all running scripts • server - show server load and memory statistics • service - manage system services • sessions - check your connected session(s) • set - set attribute on an object or account • setdesc - describe yourself • sethelp - Edit the help database. • sethome - set an object’s home location • shutdown - stop the server completely • spawn - spawn objects from prototype • style - In-game style options • tag - handles the tags of an object • tel - teleport object to another location • time - show server time statistics • tunnel - create new rooms in cardinal directions only • typeclass - set or change an object’s typeclass • unban - remove a ban from an account • unlink - remove exit-connections between rooms • userpassword - change the password of an account • wall - make an announcement to all • whisper - Speak privately as your character to another • who - list who is currently online • wipe - clear all attributes from an object

14.2.2 A-Z by source file

• account.py • admin.py • batchprocess.py • building.py • comms.py • general.py • help.py • system.py • unloggedin.py

14.2. Default Command Help 421 Evennia Documentation, Release 0.9

14.2.3 Command details

These are generated from the auto-documentation and are ordered by their source file location in evennia/commands/default/

account.py

View account.py source

charcreate (CmdCharCreate)

create a new character

Usage: charcreate[= desc]

Create a new character, optionally giving it a description. You may use upper-case letters in the name- you will nevertheless always be able to access your character using lower-case letters if you want.

• key: charcreate • aliases: • ‘locks‘_: “cmd:pperm(Player)” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdCharCreate in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py.

chardelete (CmdCharDelete)

delete a character - this cannot be undone!

Usage: chardelete

Permanently deletes one of your characters.

• key: chardelete • aliases: • ‘locks‘_: “cmd:pperm(Player)” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdCharDelete in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py.

422 Chapter 14. Appendix Evennia Documentation, Release 0.9 color (CmdColorTest) testing which colors your client support

Usage: color ansi||xterm256

Prints a color map along with in-mud color codes to use to produce them. It also tests what is supported in your client. Choices are 16-color ansi (supported in most muds) or the 256-color xterm256 standard. No checking is done to determine your client supports color- if not you will see rubbish appear.

• key: color • aliases: • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdColorTest in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. ic (CmdIC) control an object you have permission to puppet

Usage: ic

Go in-character (IC) as a given Character.

This will attempt to "become" a different object assuming you have the right to do so. Note that it's the ACCOUNT character that puppets characters/objects and which needs to have the correct permission!

You cannot become an object that is already controlled by another account. In principle can be any in-game object as long as you the account have access right to puppet it.

• key: ic • aliases: puppet • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdIC in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. look (CmdOOCLook) look while out-of-character

(continues on next page)

14.2. Default Command Help 423 Evennia Documentation, Release 0.9

(continued from previous page) Usage: look

Look in the ooc state.

• key: look • aliases: l, ls • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdOOCLook in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py.

ooc (CmdOOC)

stop puppeting and go ooc

Usage: ooc

Go out-of-character (OOC).

This will leave your current character and put you in a incorporeal OOC state.

• key: ooc • aliases: unpuppet • ‘locks‘_: “cmd:pperm(Player)” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdOOC in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. option (CmdOption)

Set an account option

Usage: option[/save] [name= value]

Switches: save- Save the current option settings for future logins. clear- Clear the saved options.

This command allows for viewing and setting client interface settings. Note that saved options may not be able to be used if later connecting with a client with different capabilities.

• key: option • aliases: options

424 Chapter 14. Appendix Evennia Documentation, Release 0.9

• ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdOption in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. password (CmdPassword) change your password

Usage: password=

Changes your password. Make sure to pick a safe one.

• key: password • aliases: • ‘locks‘_: “cmd:pperm(Player)” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdPassword in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. quell (CmdQuell) use character's permissions instead of account's

Usage: quell unquell

Normally the permission level of the Account is used when puppeting a Character/Object to determine access. This command will switch the lock system to make use of the puppeted Object's permissions instead. This is useful mainly for testing. Hierarchical permission quelling only work downwards, thus an Account cannot use a higher-permission Character to escalate their permission level. Use the unquell command to revert back to normal operation.

• key: quell • aliases: unquell • ‘locks‘_: “cmd:pperm(Player)” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdQuell in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py.

14.2. Default Command Help 425 Evennia Documentation, Release 0.9

quit (CmdQuit)

quit the game

Usage: quit

Switch: all- disconnect all connected sessions

Gracefully disconnect your current session from the game. Use the/all switch to disconnect from all sessions.

• key: quit • aliases: • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdQuit in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py.

sessions (CmdSessions)

check your connected session(s)

Usage: sessions

Lists the sessions currently connected to your account.

• key: sessions • aliases: • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdSessions in account.py. Belongs to command set ‘DefaultSession’ of class SessionCmdSet in cmdset_session.py.

style (CmdStyle)

In-game style options

Usage: style style

Configure stylings for in-game display elements like table borders, help entriest etc. Use without arguments to see all available options.

• key: style

426 Chapter 14. Appendix Evennia Documentation, Release 0.9

• aliases: • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdStyle in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. who (CmdWho) list who is currently online

Usage: who doing

Shows who is currently online. Doing is an alias that limits info also for those with all permissions.

• key: who • aliases: doing • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdWho in account.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. admin.py

View admin.py source ban (CmdBan) ban an account from the server

Usage: ban [ [: reason]]

Without any arguments, shows numbered list of active bans.

This command bans a user from accessing the game. Supply an optional reason to be able to later remember why the ban was put in place.

It is often preferable to ban an account from the server than to delete an account with accounts/delete. If banned by name, that account account can no longer be logged into.

IP (Internet Protocol) address banning allows blocking all access froma specific address or subnet. Use an asterisk (*) as a wildcard.

Examples: (continues on next page)

14.2. Default Command Help 427 Evennia Documentation, Release 0.9

(continued from previous page) ban thomas- ban account'thomas' ban/ip 134.233.2.111- ban specific ip address ban/ip 134.233.2.* - ban all in a subnet ban/ip 134.233.*.* - even wider ban

A single IP filter can be easy to circumvent by changing computers or requesting a new IP address. Setting a wide IP block filter with wildcards might be tempting, but remember that it may also accidentally block innocent users connecting from the same country or region.

• key: ban • aliases: bans • ‘locks‘_: “cmd:perm(ban) or perm(Developer)” • ‘‘‘help_category‘‘‘_: “Admin” • Source: class CmdBan in admin.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. boot (CmdBoot) kick an account from the server.

Usage boot[/switches] [: reason]

Switches: quiet- Silently boot without informing account sid- boot by session id instead of name or dbref

Boot an account object from the server. If a reason is supplied it will be echoed to the user unless/quiet is set.

• key: boot • aliases: • ‘locks‘_: “cmd:perm(boot) or perm(Admin)” • ‘‘‘help_category‘‘‘_: “Admin” • Source: class CmdBoot in admin.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. emit (CmdEmit) admin command for emitting message to multiple objects

Usage: emit[/switches] [,,...=] remit [,,...=] pemit [,,...=]

(continues on next page)

428 Chapter 14. Appendix Evennia Documentation, Release 0.9

(continued from previous page) Switches: room- limit emits to rooms only (default) accounts- limit emits to accounts only contents- send to the contents of matched objects too

Emits a message to the selected objects or to your immediate surroundings. If the object is a room, send to its contents. remit and pemit are just limited forms of emit, for sending to rooms and to accounts respectively.

• key: emit • aliases: remit, pemit • ‘locks‘_: “cmd:perm(emit) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Admin” • Source: class CmdEmit in admin.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. force (CmdForce) forces an object to execute a command

Usage: force=

Example: force bob=get stick

• key: force • aliases: • ‘locks‘_: “cmd:perm(spawn) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdForce in admin.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. perm (CmdPerm) set the permissions of an account/object

Usage: perm[/switch][=[,,...]] perm[/switch] *[=[,,...]]

Switches: del - delete the given permission from or . account- set permission on an account (same as adding * to name)

(continues on next page)

14.2. Default Command Help 429 Evennia Documentation, Release 0.9

(continued from previous page) This command sets/clears individual permission strings on an object or account. If no permission is given, list all permissions on.

• key: perm • aliases: setperm • ‘locks‘_: “cmd:perm(perm) or perm(Developer)” • ‘‘‘help_category‘‘‘_: “Admin” • Source: class CmdPerm in admin.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. unban (CmdUnban)

remove a ban from an account

Usage: unban

This will clear an account name/ip ban previously set with the ban command. Use this command without an argument to view a numbered list of bans. Use the numbers in this list to select which one to unban.

• key: unban • aliases: • ‘locks‘_: “cmd:perm(unban) or perm(Developer)” • ‘‘‘help_category‘‘‘_: “Admin” • Source: class CmdUnban in admin.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. userpassword (CmdNewPassword)

change the password of an account

Usage: userpassword=

Set an account's password.

• key: userpassword • aliases: • ‘locks‘_: “cmd:perm(newpassword) or perm(Admin)” • ‘‘‘help_category‘‘‘_: “Admin” • Source: class CmdNewPassword in admin.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py.

430 Chapter 14. Appendix Evennia Documentation, Release 0.9 wall (CmdWall) make an announcement to all

Usage: wall

Announces a message to all connected sessions including all currently unlogged in.

• key: wall • aliases: • ‘locks‘_: “cmd:perm(wall) or perm(Admin)” • ‘‘‘help_category‘‘‘_: “Admin” • Source: class CmdWall in admin.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. batchprocess.py

View batchprocess.py source batchcode (CmdBatchCode) build from batch-code file

Usage: batchcode[/interactive]

Switch: interactive- this mode will offer more control when executing the batch file, like stepping, skipping, reloading etc. debug- auto-delete all objects that has been marked as deletable in the script file (see example files for syntax). This is useful so as to to not leave multiple object copies behind when testing out the script.

Runs batches of commands froma batch-code text file (*.py).

• key: batchcode • aliases: batchcodes • ‘locks‘_: “cmd:superuser()” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdBatchCode in batchprocess.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py.

14.2. Default Command Help 431 Evennia Documentation, Release 0.9 batchcommands (CmdBatchCommands) build from batch-command file

Usage: batchcommands[/interactive]

Switch: interactive- this mode will offer more control when executing the batch file, like stepping, skipping, reloading etc.

Runs batches of commands froma batch-cmd text file (*.ev).

• key: batchcommands • aliases: batchcmd, batchcommand • ‘locks‘_: “cmd:perm(batchcommands) or perm(Developer)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdBatchCommands in batchprocess.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. building.py

View building.py source alias (CmdSetObjAlias) adding permanent aliases for object

Usage: alias [= [alias[,alias,alias,...]]] alias = alias/category = [alias[,alias,...]:

Switches: category - requires ending input with :category, to store the given aliases with the given category.

Assigns aliases to an object so it can be referenced by more than one name. Assign empty to remove all aliases from object. If assigning a category, all aliases given will be using this category.

Observe that this is not the same thing as personal aliases created with the 'nick' command! Aliases set with alias are changing the object in question, making those aliases usable by everyone.

• key: alias • aliases: setobjalias • ‘locks‘_: “cmd:perm(setobjalias) or perm(Builder)”

432 Chapter 14. Appendix Evennia Documentation, Release 0.9

• ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdSetObjAlias in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. cmdsets (CmdListCmdSets)

list command sets defined on an object

Usage: cmdsets

This displays all cmdsets assigned to a user. Defaults to yourself.

• key: cmdsets • aliases: listcmsets • ‘locks‘_: “cmd:perm(listcmdsets) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdListCmdSets in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. command (ObjManipCommand)

This is a parent class for some of the defining objmanip commands since they tend to have some more variables to define new objects.

Each object definition can have several components. First is always a name, followed by an optional alias list and finally an some optional data, such as a typeclass or a location. A comma',' separates different objects. Like this:

name1;alias;alias;alias:option, name2;alias;alias...

Spaces between all components are stripped.

A second situation is attribute manipulation. Such commands are simpler and offer combinations

objname/attr/attr/attr, objname/attr,...

• key: command • aliases: • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class ObjManipCommand in building.py. Belongs to command set ‘’ of class in ‘ ‘__.

14.2. Default Command Help 433 Evennia Documentation, Release 0.9 copy (CmdCopy) copy an object and its properties

Usage: copy[/reset][=][;alias;alias..] [:] [,...] switch: reset- make a'clean' copy off the object, thus removing any changes that might have been made to the original since it was first created.

Create one or more copies of an object. If you don't supply any targets, one exact copy of the original object will be created with the name *_copy.

• key: copy • aliases: • ‘locks‘_: “cmd:perm(copy) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdCopy in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. cpattr (CmdCpAttr) copy attributes between objects

Usage: cpattr[/switch]/=/[,/,/,...] cpattr[/switch]/=[,,,...] cpattr[/switch]=/[,/,/,...] cpattr[/switch]=[,,,...]

Switches: move- delete the attribute from the source object after copying.

Example: cpattr coolness= Anna/chillout, Anna/nicety, Tom/nicety -> copies the coolness attribute (defined on yourself), to attributes on Anna and Tom.

Copy the attribute one object to one or more attributes on another object. If you don't supply a source object, yourself is used.

• key: cpattr • aliases: • ‘locks‘_: “cmd:perm(cpattr) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdCpAttr in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py.

434 Chapter 14. Appendix Evennia Documentation, Release 0.9 create (CmdCreate) create new objects

Usage: create[/drop][;alias;alias...][:typeclass],... switch: drop- automatically drop the new object into your current location (this is not echoed). This also sets the new object's home to the current location rather than to you.

Creates one or more new objects. If typeclass is given, the object is created as a child of this typeclass. The typeclass script is assumed to be located under types/ and any further directory structure is given in Python notation. So if you have a correct typeclass'RedButton' defined in types/examples/red_button.py, you could create a new object of this type like this:

create/drop button;red : examples.red_button.RedButton

• key: create • aliases: • ‘locks‘_: “cmd:perm(create) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdCreate in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. desc (CmdDesc) describe an object or the current room.

Usage: desc [=]

Switches: edit- Open up a line editor for more advanced editing.

Sets the"desc" attribute on an object. If an object is not given, describe the current room.

• key: desc • aliases: describe • ‘locks‘_: “cmd:perm(desc) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdDesc in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py.

14.2. Default Command Help 435 Evennia Documentation, Release 0.9 destroy (CmdDestroy) permanently delete objects

Usage: destroy[/switches] [obj, obj2, obj3, [dbref-dbref],...]

Switches: override- The destroy command will usually avoid accidentally destroying account objects. This switch overrides this safety. force- destroy without confirmation. Examples: destroy house, roof, door, 44-78 destroy5-10, flower, 45 destroy/force north

Destroys one or many objects. If dbrefs are used, a range to delete can be given, e.g.4-10. Also the end points will be deleted. This command displays a confirmation before destroying, to make sure of your choice. You can specify the/force switch to bypass this confirmation.

• key: destroy • aliases: del, delete • ‘locks‘_: “cmd:perm(destroy) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdDestroy in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. dig (CmdDig) build new rooms and connect them to the current location

Usage: dig[/switches][;alias;alias...][:typeclass] [=[;alias][:typeclass]] [,[;alias][:typeclass]]

Switches: tel or teleport- move yourself to the new room

Examples: dig kitchen= north;n, south;s dig house:myrooms.MyHouseTypeclass dig sheer cliff;cliff;sheer= climb up, climb down

This command is a convenient way to build rooms quickly; it creates the new room and you can optionally set up exits back and forth between your current room and the new one. You can add as many aliases as you like to the name of the room and the exits in question; an example would be'north;no;n'.

• key: dig • aliases:

436 Chapter 14. Appendix Evennia Documentation, Release 0.9

• ‘locks‘_: “cmd:perm(dig) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdDig in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. examine (CmdExamine) get detailed information about an object

Usage: examine [[/attrname]] examine [*[/attrname]]

Switch: account- examine an Account (same as adding *) object- examine an Object (useful when OOC)

The examine command shows detailed game info about an object and optionally a specific attribute on it. If object is not specified, the current location is examined.

Append a * before the search string to examine an account.

• key: examine • aliases: exam, ex • ‘locks‘_: “cmd:perm(examine) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdExamine in building.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py.

find (CmdFind) search the database for objects

Usage: find[/switches][= dbrefmin[-dbrefmax]] locate- this is a shorthand for using the/loc switch.

Switches: room- only look for rooms (location=None) exit- only look for exits (destination!=None) char- only look for characters (BASE_CHARACTER_TYPECLASS) exact- only exact matches are returned. loc- display object location if exists and match has one result startswith- search for names starting with the string, rather than containing

Searches the database for an object of a particular name or exact #dbref. Use *accountname to search for an account. The switches allows for limiting object matches to certain game entities. Dbrefmin and dbrefmax limits matches to within the given dbrefs range, or above/below if only one is given.

14.2. Default Command Help 437 Evennia Documentation, Release 0.9

• key: find • aliases: locate, search • ‘locks‘_: “cmd:perm(find) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdFind in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. link (CmdLink) link existing rooms together with exits

Usage: link[/switches]= link[/switches]= link[/switches]

Switch: twoway- connect two exits. For this to work, BOTH and must be exit objects.

If is an exit, set its destination to. Two-way operation instead sets the destination to the *locations* of the respective given arguments. The second form (a lone=) sets the destination to None (same as the unlink command) and the third form (without=) just shows the currently set destination.

• key: link • aliases: • ‘locks‘_: “cmd:perm(link) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdLink in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. lock (CmdLock) assign a lock definition to an object

Usage: lock[=] or lock[/switch]/

Switch: del - delete given access type view- view lock associated with given access type (default)

If no lockstring is given, shows all locks on object. (continues on next page)

438 Chapter 14. Appendix Evennia Documentation, Release 0.9

(continued from previous page)

Lockstring is of the form access_type:[NOT] func1(args)[ AND|OR][ NOT] func2(args)...] Where func1, func2... valid lockfuncs with or without arguments. Separator expressions need not be capitalized.

For example: 'get: id(25) or perm(Admin)' The'get' lock access_type is checked e.g. by the'get' command. An object locked with this example lock will only be possible to pick up by Admins or by an object with id=25.

You can add several access_types after one another by separating them by';', i.e: 'get:id(25); delete:perm(Builder)'

• key: lock • aliases: locks • ‘locks‘_: “cmd: perm(locks) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdLock in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. mvattr (CmdMvAttr) move attributes between objects

Usage: mvattr[/switch]/=/[,/,/,...] mvattr[/switch]/=[,,,...] mvattr[/switch]=/[,/,/,...] mvattr[/switch]=[,,,...]

Switches: copy- Don't delete the original after moving.

Move an attribute from one object to one or more attributes on another object. If you don't supply a source object, yourself is used.

• key: mvattr • aliases: • ‘locks‘_: “cmd:perm(mvattr) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdMvAttr in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py.

14.2. Default Command Help 439 Evennia Documentation, Release 0.9 name (CmdName) change the name and/or aliases of an object

Usage: name=;alias1;alias2

Rename an object to something new. Use *obj to rename an account.

• key: name • aliases: rename • ‘locks‘_: “cmd:perm(rename) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdName in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. open (CmdOpen) open a new exit from the current room

Usage: open[;alias;alias..][:typeclass] [,< return exit>[;alias;..

˓→][:typeclass]]]=

Handles the creation of exits. If a destination is given, the exit will point there. The< return exit> argument sets up an exit at the destination leading back to the current room. Destination name can be given both as a #dbref and a name, if that name is globally unique.

• key: open • aliases: • ‘locks‘_: “cmd:perm(open) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdOpen in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. script (CmdScript) attach a script to an object

Usage: script[/switch][= script_path or ]

Switches: start- start all non-running scripts on object, or a given script only stop- stop all scripts on objects, or a given script only (continues on next page)

440 Chapter 14. Appendix Evennia Documentation, Release 0.9

(continued from previous page)

If no script path/key is given, lists all scripts active on the given object. Script path can be given from the base location for scripts as given in settings. If adding a new script, it will be started automatically (no/start switch is needed). Using the/start or /stop switches on an object without specifying a script key/path will start/stop ALL scripts on the object.

• key: script • aliases: addscript • ‘locks‘_: “cmd:perm(script) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdScript in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. set (CmdSetAttribute) set attribute on an object or account

Usage: set/= set/= set/ set */=

Switch: edit: Open the line editor (string values only) script: If we're trying to set an attribute on a script channel: If we're trying to set an attribute on a channel account: If we're trying to set an attribute on an account room: Setting an attribute on a room (global search) exit: Setting an attribute on an exit (global search) char: Setting an attribute on a character (global search) character: Alias for char, as above.

Sets attributes on objects. The second example form above clears a previously set attribute while the third form inspects the current value of the attribute (if any). The last one (with the star) is a shortcut for operating on a player Account rather than an Object.

The most common data to save with this command are strings and numbers. You can however also set Python primitives such as lists, dictionaries and tuples on objects (this might be important for the functionality of certain custom objects). This is indicated by you starting your value with one of|c'|n, |c"|n, |c(|n, |c[|n or |c{|n.

Once you have stored a Python primitive as noted above, you can include |c[]|n in to reference nested values in e.g.a list or dict.

Remember that if you use Python primitives like this, you must (continues on next page)

14.2. Default Command Help 441 Evennia Documentation, Release 0.9

(continued from previous page) write proper Python syntax too- notably you must include quotes around your strings or you will get an error.

• key: set • aliases: • ‘locks‘_: “cmd:perm(set) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdSetAttribute in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. sethome (CmdSetHome) set an object's home location

Usage: sethome[=] sethom

The"home" location is a"safety" location for objects; they will be moved there if their current location ceases to exist. All objects should always have a home location for this reason. It is also a convenient target of the"home" command.

If no location is given, just view the object's home location.

• key: sethome • aliases: • ‘locks‘_: “cmd:perm(sethome) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdSetHome in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. spawn (CmdSpawn) spawn objects from prototype

Usage: spawn[/noloc] spawn[/noloc]

spawn/search [prototype_keykey][;tag[,tag]] spawn/list [tag, tag,...] spawn/show [] spawn/update

spawn/save spawn/edit [] olc- equivalent to spawn/edit (continues on next page)

442 Chapter 14. Appendix Evennia Documentation, Release 0.9

(continued from previous page)

Switches: noloc- allow location to be None if not specified explicitly. Otherwise, location will default to caller's current location. search- search prototype by name or tags. list- list available prototypes, optionally limit by tags. show, examine- inspect prototype by key. If not given, acts like list. save- save a prototype to the database. It will be listable by/list. delete- remove a prototype from database, if allowed to. update- find existing objects with the same prototype_key and update them with latest version of given prototype. If given with /save, will auto-update all objects with the old version of the prototype without asking first. edit, olc- create/manipulate prototype in a menu interface.

Example: spawn GOBLIN spawn {"key":"goblin","typeclass":"monster.Monster","location":"#2"} spawn/save {"key":"grunt", prototype:"goblin"};;mobs;edit:all()

Dictionary keys: |wprototype_parent|n- name of parent prototype to use. Required if typeclass is not set. Can be a path or a list for multiple inheritance

˓→(inherits left to right). If set one of the parents must have a typeclass. |wtypeclass|n- string. Required if prototype_parent is not set. |wkey|n- string, the main object identifier |wlocation|n- this should be a valid object or #dbref |whome|n- valid object or #dbref |wdestination|n- only valid for exits (object or dbref) |wpermissions|n- string or list of permission strings |wlocks|n- a lock-string |waliases|n- string or list of strings. |wndb_|n- value of a nattribute (ndb_ is stripped)

|wprototype_key|n- name of this prototype. Unique. Used to store/retrieve from db and update existing prototyped objects if desired. |wprototype_desc|n- desc of this prototype. Used in listings |wprototype_locks|n- locks of this prototype. Limits who may use prototype |wprototype_tags|n- tags of this prototype. Used to find prototype

any other keywords are interpreted as Attributes and their values.

The available prototypes are defined globally in modules set in settings.PROTOTYPE_MODULES. If spawn is used without arguments it displays a list of available prototypes.

• key: spawn • aliases: olc • ‘locks‘_: “cmd:perm(spawn) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdSpawn in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py.

14.2. Default Command Help 443 Evennia Documentation, Release 0.9 tag (CmdTag) handles the tags of an object

Usage: tag[/del][=[:]] tag/search[:

Switches: search- return all objects with a given Tag del - remove the given tag. If no tag is specified, clear all tags on object.

Manipulates and lists tags on objects. Tags allow for quick grouping of and searching for objects. If only is given, list all tags on the object. If/search is used, list objects with the given tag. The category can be used for grouping tags themselves, but it should be used with restrain- tags on their own are usually enough to for most grouping schemes.

• key: tag • aliases: tags • ‘locks‘_: “cmd:perm(tag) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdTag in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. tel (CmdTeleport) teleport object to another location

Usage: tel/switch [ to||=]

Examples: tel Limbo tel/quiet box= Limbo tel/tonone box

Switches: quiet- don't echo leave/arrive messages to the source/target locations for the move. intoexit- if target is an exit, teleport INTO the exit object instead of to its destination tonone- if set, teleport the object to a None-location. If this switch is set, is ignored. Note that the only way to retrieve an object froma None location is by direct #dbref reference. A puppeted object cannot be moved to None. loc- teleport object to the target's location instead of its contents

(continues on next page)

444 Chapter 14. Appendix Evennia Documentation, Release 0.9

(continued from previous page) Teleports an object somewhere. If no object is given, you yourself are teleported to the target location.

• key: tel • aliases: teleport • ‘locks‘_: “cmd:perm(teleport) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdTeleport in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. tunnel (CmdTunnel) create new rooms in cardinal directions only

Usage: tunnel[/switch][:typeclass] [=[;alias;alias;...][:typeclass]]

Switches: oneway- do not create an exit back to the current location tel- teleport to the newly created room

Example: tunnel n tunnel n= house;mike's place;green building

This is a simple way to build using pre-defined directions: |wn,ne,e,se,s,sw,w,nw|n (north, northeast etc) |wu,d|n (up and down) |wi,o|n (in and out) The full names (north, in, southwest, etc) will always be put as main name for the exit, using the abbreviation as an alias (so an exit will always be able to be used with both"north" as well as "n" for example). Opposite directions will automatically be created back from the new room unless the/oneway switch is given. For more flexibility and power in creating rooms, use dig.

• key: tunnel • aliases: tun • ‘locks‘_: “cmd: perm(tunnel) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdTunnel in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. typeclass (CmdTypeclass) set or change an object's typeclass

Usage: (continues on next page)

14.2. Default Command Help 445 Evennia Documentation, Release 0.9

(continued from previous page) typeclass[/switch][= typeclass.path] typeclass/prototype= prototype_key

typeclass/list/show [typeclass.path] swap- this is a shorthand for using/force/reset flags. update- this is a shorthand for using the/force/reload flag.

Switch: show, examine- display the current typeclass of object (default) or, if given a typeclass path, show the docstring of that typeclass. update- *only* re-run at_object_creation on this object meaning locks or other properties set later may remain. reset- clean out *all* the attributes and properties on the object- basically making this a new clean object. force- change to the typeclass also if the object already has a typeclass of the same name. list- show available typeclasses. Only typeclasses in modules actually imported or used from somewhere in the code will show up here (those typeclasses are still available if you know the path) prototype- clean and overwrite the object with the specified prototype key- effectively making a whole new object.

Example: type button= examples.red_button.RedButton type/prototype button=a red button

If the typeclass_path is not given, the current object's typeclass is assumed.

View or set an object's typeclass. If setting, the creation hooks of the new typeclass will be run on the object. If you have clashing properties on the old class, use/reset. By default you are protected from changing to a typeclass of the same name as the one you already have- use/force to override this protection.

The given typeclass must be identified by its location using python dot-notation pointing to the correct module and class. If no typeclass is given (or a wrong typeclass is given). Errors in the path or new typeclass will lead to the old typeclass being kept. The location of the typeclass module is searched from the default typeclass directory, as defined in the server settings.

• key: typeclass • aliases: swap, parent, type, update • ‘locks‘_: “cmd:perm(typeclass) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdTypeclass in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. unlink (CmdUnLink)

446 Chapter 14. Appendix Evennia Documentation, Release 0.9

remove exit-connections between rooms

Usage: unlink

Unlinks an object, for example an exit, disconnecting it from whatever it was connected to.

• key: unlink • aliases: • ‘locks‘_: “cmd:perm(unlink) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdUnLink in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. wipe (CmdWipe)

clear all attributes from an object

Usage: wipe[/[/...]]

Example: wipe box wipe box/colour

Wipes all of an object's attributes, or optionally only those matching the given attribute-wildcard search string.

• key: wipe • aliases: • ‘locks‘_: “cmd:perm(wipe) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “Building” • Source: class CmdWipe in building.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. comms.py

View comms.py source addcom (CmdAddCom)

add a channel alias and/or subscribe to a channel

Usage: addcom [alias=]

(continues on next page)

14.2. Default Command Help 447 Evennia Documentation, Release 0.9

(continued from previous page) Joins a given channel. If alias is given, this will allow you to refer to the channel by this alias rather than the full channel name. Subsequent calls of this command can be used to add multiple aliases to an already joined channel.

• key: addcom • aliases: aliaschan, chanalias • ‘locks‘_: “cmd:not pperm(channel_banned)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdAddCom in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. allcom (CmdAllCom) perform admin operations on all channels

Usage: allcom [on| off| who| destroy]

Allows the user to universally turn off or on all channels they are on, as well as perform a'who' for all channels they are on. Destroy deletes all channels that you control.

Without argument, works like comlist.

• key: allcom • aliases: • ‘locks‘_: “cmd: not pperm(channel_banned)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdAllCom in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. cboot (CmdCBoot) kick an account froma channel you control

Usage: cboot[/quiet]= [:reason]

Switch: quiet- don't notify the channel

Kicks an account or object froma channel you control.

• key: cboot • aliases: • ‘locks‘_: “cmd: not pperm(channel_banned)”

448 Chapter 14. Appendix Evennia Documentation, Release 0.9

• ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdCBoot in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. ccreate (CmdChannelCreate)

create a new channel

Usage: ccreate[;alias;alias...]= description

Creates a new channel owned by you.

• key: ccreate • aliases: channelcreate • ‘locks‘_: “cmd:not pperm(channel_banned) and pperm(Player)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdChannelCreate in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. cdesc (CmdCdesc)

describe a channel you control

Usage: cdesc=

Changes the description of the channel as shown in channel lists.

• key: cdesc • aliases: • ‘locks‘_: “cmd:not pperm(channel_banned)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdCdesc in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. cdestroy (CmdCdestroy)

destroy a channel you created

Usage: cdestroy

Destroys a channel that you control.

• key: cdestroy

14.2. Default Command Help 449 Evennia Documentation, Release 0.9

• aliases: • ‘locks‘_: “cmd: not pperm(channel_banned)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdCdestroy in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py.

cemit (CmdCemit)

send an admin message to a channel you control

Usage: cemit[/switches]=

Switches: sendername- attach the sender's name before the message quiet- don't echo the message back to sender

Allows the user to broadcast a message over a channel as long as they control it. It does not show the user's name unless they provide the/sendername switch.

• key: cemit • aliases: cmsg • ‘locks‘_: “cmd: not pperm(channel_banned) and pperm(Player)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdCemit in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. channels (CmdChannels)

list all channels available to you

Usage: channels clist comlist

Lists all channels available to you, whether you listen to them or not. Use'comlist' to only view your current channel subscriptions. Use addcom/delcom to join and leave channels

• key: channels • aliases: chanlist, channellist, clist, comlist, all channels • ‘locks‘_: “cmd: not pperm(channel_banned)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdChannels in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py.

450 Chapter 14. Appendix Evennia Documentation, Release 0.9 clock (CmdClock) change channel locks of a channel you control

Usage: clock[=]

Changes the lock access restrictions of a channel. If no lockstring was given, view the current lock definitions.

• key: clock • aliases: • ‘locks‘_: “cmd:not pperm(channel_banned)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdClock in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. cwho (CmdCWho) show who is listening to a channel

Usage: cwho

List who is connected to a given channel you have access to.

• key: cwho • aliases: • ‘locks‘_: “cmd: not pperm(channel_banned)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdCWho in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. delcom (CmdDelCom) remove a channel alias and/or unsubscribe from channel

Usage: delcom delcom/all

If the full channel name is given, unsubscribe from the channel. If an alias is given, remove the alias but don't unsubscribe. If the'all' switch is used, remove all aliases for that channel.

• key: delcom • aliases: delaliaschan, delchanalias

14.2. Default Command Help 451 Evennia Documentation, Release 0.9

• ‘locks‘_: “cmd:not perm(channel_banned)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdDelCom in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. irc2chan (CmdIRC2Chan)

Link an evennia channel to an external IRC channel

Usage: irc2chan[/switches]=< #irchannel>

˓→[:typeclass] irc2chan/delete botname|#dbid

Switches: /delete- this will delete the bot and remove the irc connection to the channel. Requires the botname or #dbid as input. /remove- alias to/delete /disconnect- alias to/delete /list- show all irc<->evennia mappings /ssl- use an SSL-encrypted connection

Example: irc2chan myircchan= irc..net 6667 #mychannel evennia-bot irc2chan public= irc.freenode.net 6667 #evgaming #evbot:accounts.mybot.MyBot

This creates an IRC bot that connects to a given IRC network and channel. If a custom typeclass path is given, this will be used instead of the default bot class. The bot will relay everything said in the evennia channel to the IRC channel and vice versa. The bot will automatically connect at server start, so this command need only be given once. The /disconnect switch will permanently delete the bot. To only temporarily deactivate it, use the|wservices|n command instead. Provide an optional bot class path to use a custom bot.

• key: irc2chan • aliases: • ‘locks‘_: “cmd:serversetting(IRC_ENABLED) and pperm(Developer)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdIRC2Chan in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. page (CmdPage) send a private message to another account

Usage: page[/switches] [,,...=] tell'' (continues on next page)

452 Chapter 14. Appendix Evennia Documentation, Release 0.9

(continued from previous page) page

Switch: last- shows who you last messaged list- show your last of tells/pages (default)

Send a message to target user (if online). If no argument is given, you will get a list of your latest messages.

• key: page • aliases: tell • ‘locks‘_: “cmd:not pperm(page_banned)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdPage in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py. rss2chan (CmdRSS2Chan) link an evennia channel to an external RSS feed

Usage: rss2chan[/switches]=

Switches: /disconnect- this will stop the feed and remove the connection to the channel. /remove-" /list- show all rss->evennia mappings

Example: rss2chan rsschan= http://code.google.com/feeds/p/evennia/updates/basic

This creates an RSS reader that connects to a given RSS feed url. Updates will be echoed as a title and news link to the given channel. The rate of updating is set with the RSS_UPDATE_INTERVAL variable in settings (default is every 10 minutes).

When disconnecting you need to supply both the channel and url again so as to identify the connection uniquely.

• key: rss2chan • aliases: • ‘locks‘_: “cmd:serversetting(RSS_ENABLED) and pperm(Developer)” • ‘‘‘help_category‘‘‘_: “Comms” • Source: class CmdRSS2Chan in comms.py. Belongs to command set ‘DefaultAccount’ of class AccountCmdSet in cmdset_account.py.

14.2. Default Command Help 453 Evennia Documentation, Release 0.9

general.py

View general.py source

access (CmdAccess)

show your current game access

Usage: access

This command shows you the permission hierarchy and which permission groups you are a member of.

• key: access • aliases: groups, hierarchy • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdAccess in general.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py.

drop (CmdDrop)

drop something

Usage: drop

Lets you drop an object from your inventory into the location you are currently in.

• key: drop • aliases: • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdDrop in general.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. get (CmdGet)

pick up something

Usage: get

Picks up an object from your location and puts it in your inventory.

454 Chapter 14. Appendix Evennia Documentation, Release 0.9

• key: get • aliases: grab • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdGet in general.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. give (CmdGive)

give away something to someone

Usage: give

Gives an items from your inventory to another character, placing it in their inventory.

• key: give • aliases: • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdGive in general.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. home (CmdHome)

move to your character's home location

Usage: home

Teleports you to your home location.

• key: home • aliases: • ‘locks‘_: “cmd:perm(home) or perm(Builder)” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdHome in general.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. inventory (CmdInventory)

14.2. Default Command Help 455 Evennia Documentation, Release 0.9

view inventory

Usage: inventory inv

Shows your inventory.

• key: inventory • aliases: i, inv • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdInventory in general.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. look (CmdLook) look at location or object

Usage: look look look *

Observes your location or objects in your vicinity.

• key: look • aliases: l, ls • ‘locks‘_: “cmd:all()” • ‘‘‘help_category‘‘‘_: “General” • Source: class CmdLook in general.py. Belongs to command set ‘DefaultCharacter’ of class CharacterCmdSet in cmdset_character.py. nick (CmdNick) define a personal alias/nick by defining a string to match and replace it with another on the fly

Usage: nick[/switches] [= [replacement_string]] nick[/switches]