Working on Components docs

This commit is contained in:
Griatch 2022-11-25 23:48:51 +01:00
parent ff6e01475d
commit b21cd5952c
5 changed files with 196 additions and 281 deletions

View file

@ -22,7 +22,7 @@ how many tests were run and how long it took. If something went wrong you will g
If you contribute to Evennia, this is a useful sanity check to see you haven't introduced an If you contribute to Evennia, this is a useful sanity check to see you haven't introduced an
unexpected bug. unexpected bug.
## Running tests for your game dir ## Running custom game-dir unit tests
If you have implemented your own tests for your game you can run them from your game dir If you have implemented your own tests for your game you can run them from your game dir
with with
@ -46,7 +46,7 @@ You can also test specific things by giving their path
evennia test --settings settings.py .world.tests.YourTest evennia test --settings settings.py .world.tests.YourTest
## Writing new tests ## Writing new unit tests
Evennia's test suite makes use of Django unit test system, which in turn relies on Python's Evennia's test suite makes use of Django unit test system, which in turn relies on Python's
*unittest* module. *unittest* module.
@ -118,14 +118,14 @@ You can also run a specific test:
You might also want to read the [Python documentation for the unittest module](https://docs.python.org/library/unittest.html). You might also want to read the [Python documentation for the unittest module](https://docs.python.org/library/unittest.html).
## Using the Evennia testing classes ### Using the Evennia testing classes
Evennia offers many custom testing classes that helps with testing Evennia features. Evennia offers many custom testing classes that helps with testing Evennia features.
They are all found in [evennia.utils.test_resources](evennia.utils.test_resources). Note that They are all found in [evennia.utils.test_resources](evennia.utils.test_resources). Note that
these classes implement the `setUp` and `tearDown` already, so if you want to add stuff in them these classes implement the `setUp` and `tearDown` already, so if you want to add stuff in them
yourself you should remember to use e.g. `super().setUp()` in your code. yourself you should remember to use e.g. `super().setUp()` in your code.
### Classes for testing your game dir #### Classes for testing your game dir
These all use whatever setting you pass to them and works well for testing code in your game dir. These all use whatever setting you pass to them and works well for testing code in your game dir.
@ -198,7 +198,7 @@ the `.call` helper), `||` to indicate multiple uses of `.msg()` in the Command.
has a lot of arguments for mimicing different ways of calling a Command, so make sure to has a lot of arguments for mimicing different ways of calling a Command, so make sure to
[read the API docs for .call()](evennia.utils.test_resources.EvenniaCommandTestMixin.call). [read the API docs for .call()](evennia.utils.test_resources.EvenniaCommandTestMixin.call).
### Classes for testing Evennia core #### Classes for testing Evennia core
These are used for testing Evennia itself. They provide the same resources as the classes These are used for testing Evennia itself. They provide the same resources as the classes
above but enforce Evennias default settings found in `evennia/settings_default.py`, ignoring above but enforce Evennias default settings found in `evennia/settings_default.py`, ignoring
@ -218,7 +218,7 @@ If you want to help out writing unittests for Evennia, take a look at Evennia's
page](https://coveralls.io/github/evennia/evennia). There you see which modules have any form of page](https://coveralls.io/github/evennia/evennia). There you see which modules have any form of
test coverage and which does not. All help is appreciated! test coverage and which does not. All help is appreciated!
## Unit testing contribs with custom models ### 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 A special case is if you were to create a contribution to go to the `evennia/contrib` folder that
uses its [own database models](../Concepts/New-Models.md). The problem with this is that Evennia (and Django) will uses its [own database models](../Concepts/New-Models.md). The problem with this is that Evennia (and Django) will
@ -281,7 +281,7 @@ class TestMyModel(BaseEvenniaTest):
``` ```
## A note on making the test runner faster ### 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 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: django-test-without-migrations package. To install it, simply:

View file

@ -1,41 +1,39 @@
# Accounts # Accounts
```
┌──────┐ │ ┌───────┐ ┌───────┐ ┌──────┐
│Client├─┼──►│Session├───►│Account├──►│Object│
└──────┘ │ └───────┘ └───────┘ └──────┘
^
```
All *users* (real people) that starts a game [Session](./Sessions.md) on Evennia are doing so through an An _Account_ represents a unique game account - one player playing the game. Whereas a player can potentially connect to the game from several Clients/Sessions, they will normally have only one Account.
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](./Objects.md) The Account object has no in-game representation. In order to actually get on the game the Account must *puppet* an [Object](./Objects.md) (normally a [Character](./Objects.md#characters)).
(normally a [Character](./Objects.md#characters)).
Exactly how many Sessions can interact with an Account and its Puppets at once is determined by Exactly how many Sessions can interact with an Account and its Puppets at once is determined by
Evennia's [MULTISESSION_MODE](./Sessions.md#multisession-mode) setting. Evennia's [MULTISESSION_MODE](./Sessions.md#multisession-mode) setting.
Apart from storing login information and other account-specific data, the Account object is what is Apart from storing login information and other account-specific data, the Account object is what is chatting on Evennia's default [Channels](./Channels.md). It is also a good place to store [Permissions](./Locks.md) to be consistent between different in-game characters. It can also hold player-level configuration options.
chatting on [Channels](./Channels.md). It is also a good place to store [Permissions](./Locks.md) to be
consistent between different in-game characters as well as configuration options. The Account
object also has its own [CmdSet](./Command-Sets.md), the `AccountCmdSet`.
Logged into default evennia, you can use the `ooc` command to leave your current The Account object has its own default [CmdSet](./Command-Sets.md), the `AccountCmdSet`. The commands in this set are available to the player no matter which character they are puppeting. Most notably the default game's `exit`, `who` and chat-channel commands are in the Account cmdset.
[character](./Objects.md) 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 > ooc
[Permissions](./Permissions.md) 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).
## How to create your own Account types The default `ooc` command causes you to leave your current puppet and go into OOC mode. In this mode you have no location and have only the Account-cmdset available. It acts a staging area for switching characters (if your game supports that) as well as a safety fallback if your character gets accidentally deleted.
You will usually not want more than one Account typeclass for all new accounts (but you could in > ic
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 This re-puppets the latest character.
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`). Note that the Account object can have, and often does have, a different set of [Permissions](./Permissions.md) 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).
## How to customize your own Account types
You will usually not want more than one Account typeclass for all new accounts.
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: Here's an example of modifying the default Account class in code:
@ -44,62 +42,47 @@ Here's an example of modifying the default Account class in code:
from evennia import DefaultAccount from evennia import DefaultAccount
class Account(DefaultAccount): # [...] class Account(DefaultAccount):
# [...]
at_account_creation(self): "this is called only once, when account is first created" def at_account_creation(self):
self.db.real_name = None # this is set later self.db.real_address = None # " "this is called only once, when account is first created"
self.db.config_1 = True # default config self.db.config_2 = False # " self.db.real_name = None # this is set later
self.db.config_3 = 1 # " self.db.real_address = None # "
self.db.config_1 = True # default config
# ... whatever else our game needs to know ``` Reload the server with `reload`. self.db.config_2 = False # "
self.db.config_3 = 1 # "
# ... whatever else our game needs to know
``` ```
... However, if you use `examine *self` (the asterisk makes you examine your Account object rather Reload the server with `reload`.
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`:
... However, if you use `examine *self` (the asterisk 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()] ``` ``` py [account.at_account_creation() for account in evennia.managers.accounts.all()] ```
You should now see the Attributes on yourself. 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.
> 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.
## Properties on Accounts ## Properties on Accounts
Beyond those properties assigned to all typeclassed objects (see [Typeclasses](./Typeclasses.md)), the Beyond those properties assigned to all typeclassed objects (see [Typeclasses](./Typeclasses.md)), the Account also has the following custom properties:
Account also has the following custom properties:
- `user` - a unique link to a `User` Django object, representing the logged-in user. - `user` - a unique link to a `User` Django object, representing the logged-in user.
- `obj` - an alias for `character`. - `obj` - an alias for `character`.
- `name` - an alias for `user.username` - `name` - an alias for `user.username`
- `sessions` - an instance of - `sessions` - an instance of [ObjectSessionHandler](github:evennia.objects.objects#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.
[ObjectSessionHandler](github:evennia.objects.objects#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. - `is_superuser` (bool: True/False) - if this account is a superuser.
Special handlers: Special handlers:
- `cmdset` - This holds all the current [Commands](./Commands.md) of this Account. By default these are - `cmdset` - This holds all the current [Commands](./Commands.md) of this Account. By default these are
the commands found in the cmdset defined by `settings.CMDSET_ACCOUNT`. the commands found in the cmdset defined by `settings.CMDSET_ACCOUNT`.
- `nicks` - This stores and handles [Nicks](./Nicks.md), in the same way as nicks it works on Objects. - `nicks` - This stores and handles [Nicks](./Nicks.md), in the same way as nicks it works on Objects. For Accounts, nicks are primarily used to store custom aliases for [Channels](./Channels.md).
For Accounts, nicks are primarily used to store custom aliases for
[Channels](./Channels.md).
Selection of special methods (see `evennia.DefaultAccount` for details): 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 - `get_puppet` - get a currently puppeted object connected to the Account and a given session id, if any.
any.
- `puppet_object` - connect a session to a puppetable Object. - `puppet_object` - connect a session to a puppetable Object.
- `unpuppet_object` - disconnect a session from a puppetable Object. - `unpuppet_object` - disconnect a session from a puppetable Object.
- `msg` - send text to the Account - `msg` - send text to the Account

View file

@ -1,23 +1,53 @@
# Objects # Objects
```
┌──────┐ │ ┌───────┐ ┌───────┐ ┌──────┐
│Client├─┼──►│Session├───►│Account├──►│Object│
└──────┘ │ └───────┘ └───────┘ └──────┘
^
```
All in-game objects in Evennia, be it characters, chairs, monsters, rooms or hand grenades are 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 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](./Typeclasses.md) entities. spend most time working with. Objects are [Typeclassed](./Typeclasses.md) entities.
An Evennia Object is, by definition, a Python class that includes An Evennia Object is, by definition, a Python class that includes [evennia.objects.objects.DefaultObject](evennia.objects.objects.DefaultObject) among its parents. Evennia defines several subclasses of `DefaultObject` in the following inheritance tree:
[evennia.objects.objects.DefaultObject](evennia.objects.objects.DefaultObject) among its
parents. Evennia defines several subclasses of `DefaultObject`:
- [evennia.objects.objects.DefaultCharacter](evennia.objects.objects.DefaultCharacter) - ```
the normal in-game Character, controlled by a player. ┌────────────┐
Evennia│ │ObjectParent│
library│ └──────▲─────┘
│ │
┌─────────────┐ │ ┌──────┐ │
│DefaultObject◄────────────────────┼────┤Object├──────┤
└──────▲──────┘ │ └──────┘ │
│ │ │
│ ┌────────────────┐ │ ┌─────────┐ │
├────────┤DefaultCharacter◄─┼────┤Character├───┤
│ └────────────────┘ │ └─────────┘ │
│ │ │
│ ┌────────────────┐ │ ┌────┐ │
├────────┤DefaultRoom ◄─┼────┤Room├────────┤
│ └────────────────┘ │ └────┘ │
│ │ │
│ ┌────────────────┐ │ ┌────┐ │
└────────┤DefaultExit ◄─┼────┤Exit├────────┘
└────────────────┘ │ └────┘
│Game-dir
```
Here, arrows indicate inheritance and point from-child-to-parent.
So, for example `DefaultObjet` is a child of `DefaultCharacter` (in the Evennia library), which is a parent of `Character` (in the game dir). The class in the game-dir is the one you should modify for your game.
> Note the `ObjectParent` class. This is an empty mix-in that all classes in the game-dir inherits from. It's where you put things you want _all_ these classes to have.
- [evennia.objects.objects.DefaultCharacter](evennia.objects.objects.DefaultCharacter) - the normal in-game Character, controlled by a player.
- [evennia.objects.objects.DefaultRoom](evennia.objects.objects.DefaultRoom) - a location in the game world. - [evennia.objects.objects.DefaultRoom](evennia.objects.objects.DefaultRoom) - a location in the game world.
- [evennia.objects.objects.DefaultExit](evennia.objects.objects.DefaultExit) - an entity that (usually) sits - [evennia.objects.objects.DefaultExit](evennia.objects.objects.DefaultExit) - an entity that in a location (usually a Room). It represents a one-way connection to another location.
in a room and represents a one-way connection to another location.
You will usually not use the `Default*` parents themselves. In `mygame/typeclasses/` there are Here are the import paths for the relevant child classes in the game dir
convenient subclasses to use. They are empty, and thus identical to
the defaults. Tweaking them is one of the main ways to customize you game!
- `mygame.typeclasses.objects.Object` (inherits from `DefaultObject`) - `mygame.typeclasses.objects.Object` (inherits from `DefaultObject`)
- `mygame.typeclasses.characters.Character` (inherits from `DefaultCharacter`) - `mygame.typeclasses.characters.Character` (inherits from `DefaultCharacter`)
@ -26,12 +56,9 @@ the defaults. Tweaking them is one of the main ways to customize you game!
## How to create your own object types ## How to create your own object types
You can easily add your own in-game behavior by either modifying one of the typeclasses in You can easily add your own in-game behavior by either modifying one of the typeclasses in your game dir or by inheriting from them.
your game dir or by inheriting from them.
You can put your new typeclass directly in the relevant parent You can put your new typeclass directly in the relevant module, or you could organize your code in some other way. Here we assume we make a new module `mygame/typeclasses/flowers.py`:
module, or you could organize your code in some other way. Here we assume we make a new module
`mygame/typeclasses/flowers.py`:
```python ```python
# mygame/typeclasses/flowers.py # mygame/typeclasses/flowers.py
@ -52,7 +79,7 @@ module, or you could organize your code in some other way. Here we assume we mak
Now you just need to point to the class *Rose* with the `create` command Now you just need to point to the class *Rose* with the `create` command
to make a new rose: to make a new rose:
@create/drop MyRose:flowers.Rose create/drop MyRose:flowers.Rose
What the `create` command actually *does* is to use the [evennia.create_object](evennia.utils.create.create_object) What the `create` command actually *does* is to use the [evennia.create_object](evennia.utils.create.create_object)
function. You can do the same thing yourself in code: function. You can do the same thing yourself in code:
@ -75,11 +102,9 @@ will usually want to change this at build time (using the `desc` command or usin
## Adding common functionality ## Adding common functionality
`Object`, `Character`, `Room` and `Exit` also inherit from `mygame.typeclasses.objects.ObjectParent`. `Object`, `Character`, `Room` and `Exit` also inherit from `mygame.typeclasses.objects.ObjectParent`.
This is an empty 'mixin' class. Optionally, you can modify this class if you want to easily add some _common_ functionality to all This is an empty 'mixin' class. Optionally, you can modify this class if you want to easily add some _common_ functionality to all your Objects, Characters, Rooms and Exits at once. You can still customize each subclass separately (see the Python docs on [multiple inheritance](https://docs.python.org/3/tutorial/classes.html#multiple-inheritance) for details).
your Objects, Characters, Rooms and Exits at once. You can still customize each subclass separately (see the Python
docs on [multiple inheritance](https://docs.python.org/3/tutorial/classes.html#multiple-inheritance) for details).
For example: Here is an example:
```python ```python
# in mygame/typeclasses/objects.py # in mygame/typeclasses/objects.py
@ -91,139 +116,68 @@ class ObjectParent:
def at_pre_get(self, getter, **kwargs): def at_pre_get(self, getter, **kwargs):
# make all entities by default un-pickable # make all entities by default un-pickable
return False return False
class Object(ObjectParent, DefaultObject):
# replaces at_pre_get with its own
def at_pre_get(self, getter, **kwargs):
return True
# each in their respective modules ...
class Character(ObjectParent, DefaultCharacter):
# will inherit at_pre_get from ObjectParent
pass
class Exit(ObjectParent, DefaultExit):
# Overrides and uses the DefaultExit version of at_pre_get instead
def at_pre_get(self, getter, **kwargs):
return DefaultExit.at_pre_get(self, getter, **kwargs)
``` ```
Now all of `Object`, `Exit`. `Room` and `Character` default to not being able to be picked up using the `get` command.
## Properties and functions on Objects ## Properties and functions on Objects
Beyond the properties assigned to all [typeclassed](./Typeclasses.md) objects (see that page for a list Beyond the properties assigned to all [typeclassed](./Typeclasses.md) objects (see that page for a list
of those), the Object also has the following custom properties: 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` - 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.
`aliases.add()` to add a new alias and `aliases.remove()` to remove one.
- `location` - a reference to the object currently containing this object. - `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 - `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.
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](./Objects.md#exits), it's otherwise usually unset.
- `destination` - this holds a reference to another object this object links to in some way. Its - `nicks` - as opposed to aliases, a [Nick](./Nicks.md) 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.
main use is for [Exits](./Objects.md#exits), it's otherwise usually unset. - `account` - this holds a reference to a connected [Account](./Accounts.md) 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.
- `nicks` - as opposed to aliases, a [Nick](./Nicks.md) holds a convenient nickname replacement for a - `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).
real name, word or sequence, only valid for this object. This mainly makes sense if the Object is - `has_account` - a shorthand for checking if an *online* account is currently connected to this object.
used as a game character - it can then store briefer shorts, example so as to quickly reference game - `contents` - this returns a list referencing all objects 'inside' this object (i,e. which has this object set as their `location`).
commands or other characters. Use nicks.add(alias, realname) to add a new one. - `exits` - this returns all objects inside this object that are *Exits*, that is, has the `destination` property set.
- `account` - this holds a reference to a connected [Account](./Accounts.md) 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`).
- `exits` - this returns all objects inside this object that are *Exits*, that is, has the
`destination` property set.
The last two properties are special: The last two properties are special:
- `cmdset` - this is a handler that stores all [command sets](./Command-Sets.md) defined on the - `cmdset` - this is a handler that stores all [command sets](./Command-Sets.md) defined on the object (if any).
object (if any).
- `scripts` - this is a handler that manages [Scripts](./Scripts.md) attached to the object (if any). - `scripts` - this is a handler that manages [Scripts](./Scripts.md) attached to the object (if any).
The Object also has a host of useful utility functions. See the function headers in 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.
`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 - `msg()` - this function is used to send messages from the server to an account connected to this object.
object.
- `msg_contents()` - calls `msg` on all objects inside 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 - `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).
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. - `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 - `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.
and will call all relevant hooks, do all checks etc.
- `clear_exits()` - will delete all [Exits](./Objects.md#exits) to *and* from this object. - `clear_exits()` - will delete all [Exits](./Objects.md#exits) to *and* from this object.
- `clear_contents()` - this will not delete anything, but rather move all contents (except Exits) to - `clear_contents()` - this will not delete anything, but rather move all contents (except Exits) to their designated `Home` locations.
their designated `Home` locations. - `delete()` - deletes this object, first calling `clear_exits()` and `clear_contents()`.
- `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 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](evennia.objects.objects.DefaultObject).
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](evennia.objects.objects.DefaultObject).
## Subclasses of `Object` ## Subclasses of `Object`
There are three special subclasses of *Object* in default Evennia - *Characters*, *Rooms* and 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.
*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
Characters are objects controlled by [Accounts](./Accounts.md). When a new Account Characters are objects controlled by [Accounts](./Accounts.md). 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](./Command-Sets.md) 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.
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](./Command-Sets.md) 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.
### Rooms ### Rooms
*Rooms* are the root containers of all other objects. The only thing really separating a room from *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.
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
*Exits* are objects connecting other objects (usually *Rooms*) together. An object named *North* or *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](./Commands.md) 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.
*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](./Commands.md) 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 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](./Locks.md) 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.
change how exits work in your game (it's not recommended though, unless you really know what you are
doing). Exits are [locked](./Locks.md) 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: 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 1. The traversing `obj` sends a command that matches the Exit-command name on the Exit object. The [cmdhandler](./Commands.md) 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).
[cmdhandler](./Commands.md) 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).
1. The Exit command checks the `traverse` lock on the Exit object 1. The Exit command checks the `traverse` lock on the Exit object
1. The Exit command triggers `at_traverse(obj, destination)` on the Exit object. 1. The Exit command triggers `at_traverse(obj, destination)` on the Exit object.
1. In `at_traverse`, `object.move_to(destination)` is triggered. This triggers the following hooks, 1. In `at_traverse`, `object.move_to(destination)` is triggered. This triggers the following hooks, in order:
in order:
1. `obj.at_pre_move(destination)` - if this returns False, move is aborted. 1. `obj.at_pre_move(destination)` - if this returns False, move is aborted.
1. `origin.at_pre_leave(obj, destination)` 1. `origin.at_pre_leave(obj, destination)`
1. `obj.announce_move_from(destination)` 1. `obj.announce_move_from(destination)`
@ -233,6 +187,4 @@ in order:
1. `obj.at_post_move(source)` 1. `obj.at_post_move(source)`
1. On the Exit object, `at_post_traverse(obj, source)` is triggered. 1. On the Exit object, `at_post_traverse(obj, source)` is triggered.
If the move fails for whatever reason, the Exit will look for an Attribute `err_traverse` on itself 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.
and display this as an error message. If this is not found, the Exit will instead call
`at_failed_traverse(obj)` on itself.

View file

@ -1,23 +1,19 @@
# Sessions # Sessions
```
┌──────┐ │ ┌───────┐ ┌───────┐ ┌──────┐
│Client├─┼──►│Session├───►│Account├──►│Object│
└──────┘ │ └───────┘ └───────┘ └──────┘
^
```
An Evennia *Session* represents one single established connection to the server. Depending on the 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 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. clients in multiple windows. Each such connection is represented by a session object.
A session object has its own [cmdset](./Command-Sets.md), usually the "unloggedin" cmdset. This is what A session object has its own [cmdset](./Command-Sets.md), 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](./Accounts.md) 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.
is used to show the login screen and to handle commands to create a new account (or
[Account](./Accounts.md) 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](./Typeclasses.md) and has no A Session is not *persistent* - it is not a [Typeclass](./Typeclasses.md) 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.
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.
## Properties on Sessions ## Properties on Sessions
@ -42,41 +38,59 @@ Session statistics are mainly used internally by Evennia.
last time this session was truly visibly active. last time this session was truly visibly active.
- `cmd_total` - Total number of Commands passed through this Session. - `cmd_total` - Total number of Commands passed through this Session.
## Multisession mode ## Multisession mode
The number of sessions possible to connect to a given account at the same time and how it works is 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:
given by the `MULTISESSION_MODE` setting:
* `MULTISESSION_MODE=0`: One session per account. When connecting with a new session the old one is * `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.
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 │Client├─┼──►│Session├───►│Account├──►│Character│
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 * `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.
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. │Client├─┼──►│Session├──┐
* `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 │ │Account├──►│Character│
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 │Client├─┼──►│Session├──┘
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 * `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.
Character. ```
│ ┌───────┐
┌──────┐ │ ┌───────┐ │Account│ ┌─────────┐
│Client├─┼──►│Session├──┐ │ │ ┌►│Character│
└──────┘ │ └───────┘ └──┼───────┼──┘ └─────────┘
│ │ │
┌──────┐ │ ┌───────┐ ┌──┼───────┼──┐ ┌─────────┐
│Client├─┼──►│Session├──┘ │ │ └►│Character│
└──────┘ │ └───────┘ │ │ └─────────┘
│ └───────┘
```
* `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.
```
│ ┌───────┐
┌──────┐ │ ┌───────┐ │Account│ ┌─────────┐
│Client├─┼──►│Session├──┐ │ │ ┌►│Character│
└──────┘ │ └───────┘ └──┼───────┼──┘ └─────────┘
│ │ │
┌──────┐ │ ┌───────┐ ┌──┼───────┼──┐
│Client├─┼──►│Session├──┘ │ │ └►┌─────────┐
└──────┘ │ └───────┘ │ │ │Character│
│ │ │ ┌►└─────────┘
┌──────┐ │ ┌───────┐ ┌──┼───────┼──┘ ▼
│Client├─┼──►│Session├──┘ │ │
└──────┘ │ └───────┘ └───────┘
```
> Note that even if multiple Sessions puppet one Character, there is only ever one instance of that Character.
## Returning data to the session ## Returning data to the session

View file

@ -4,10 +4,7 @@
different game entities as Python classes, without having to modify the database schema for every different game entities as Python classes, without having to modify the database schema for every
new type. new type.
In Evennia the most important game entities, [Accounts](./Accounts.md), [Objects](./Objects.md), In Evennia the most important game entities, [Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and [Channels](./Channels.md) 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".
[Scripts](./Scripts.md) and [Channels](./Channels.md) 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: This is how the inheritance looks for the typeclasses in Evennia:
@ -66,12 +63,7 @@ default library):
pass pass
``` ```
1. A typeclass' `__init__` method should normally not be overloaded. This has mostly to do with the 1. 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:
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::
```python ```python
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -86,8 +78,7 @@ treat it as such.
## Creating a new typeclass ## Creating a new typeclass
It's easy to work with Typeclasses. Either you use an existing typeclass or you create a new Python 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:
class inheriting from an existing typeclass. Here is an example of creating a new type of Object:
```python ```python
from evennia import DefaultObject from evennia import DefaultObject
@ -247,8 +238,7 @@ you will always query all children on the database model.
## Updating existing typeclass instances ## Updating existing typeclass instances
If you already have created instances of Typeclasses, you can modify the *Python code* at any time - 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 due to how Python inheritance works your changes will automatically be applied to all children once you have reloaded the server.
you have reloaded the server.
However, database-saved data, like `db_*` fields, [Attributes](./Attributes.md), [Tags](./Tags.md) etc, are However, database-saved data, like `db_*` fields, [Attributes](./Attributes.md), [Tags](./Tags.md) etc, are
not themselves embedded into the class and will *not* be updated automatically. This you need to not themselves embedded into the class and will *not* be updated automatically. This you need to
@ -281,7 +271,7 @@ comprehensions](http://www.secnetix.de/olli/Python/list_comprehensions.hawk), li
line break, that's only for readability in the wiki): line break, that's only for readability in the wiki):
``` ```
@py from typeclasses.furniture import Furniture; py from typeclasses.furniture import Furniture;
[obj.at_object_creation() for obj in Furniture.objects.all() if not obj.db.worth] [obj.at_object_creation() for obj in Furniture.objects.all() if not obj.db.worth]
``` ```
@ -290,20 +280,15 @@ retroactively update objects more than necessary.
## Swap typeclass ## Swap typeclass
If you want to swap an already existing typeclass, there are two ways to do so: From in-game and via 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:
code. From inside the game you can use the default `@typeclass` command:
``` ```
@typeclass objname = path.to.new.typeclass typeclass objname = path.to.new.typeclass
``` ```
There are two important switches to this command: There are two important switches to this command:
- `/reset` - This will purge all existing Attributes on the object and re-run the creation hook - `/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.
(like `at_object_creation` for Objects). This assures you get an object which is purely of this new - `/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.
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: In code you instead use the `swap_typeclass` method which you can find on all typeclassed entities:
@ -312,44 +297,25 @@ obj_to_change.swap_typeclass(new_typeclass_path, clean_attributes=False,
run_start_hooks="all", no_default=True, clean_cmdsets=False) run_start_hooks="all", no_default=True, clean_cmdsets=False)
``` ```
The arguments to this method are described [in the API docs The arguments to this method are described [in the API docs here](github:evennia.typeclasses.models#typedobjectswap_typeclass).
here](github:evennia.typeclasses.models#typedobjectswap_typeclass).
## How typeclasses actually work ## How typeclasses actually work
*This is considered an advanced section.* *This is considered an advanced section.*
Technically, typeclasses are [Django proxy Technically, typeclasses are [Django proxy models](https://docs.djangoproject.com/en/1.7/topics/db/models/#proxy-models). The only database
models](https://docs.djangoproject.com/en/1.7/topics/db/models/#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](./Attributes.md) and [Tags](./Tags.md) but they are not typeclasses themselves). All the subclasses of them are "proxies", extending them with Python code without actually modifying the database layout.
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](./Attributes.md) and [Tags](./Tags.md) 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 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.
(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.
## Caveats ## Caveats
Evennia uses the *idmapper* to cache its typeclasses (Django proxy models) in memory. The idmapper 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.
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](New- There is one caveat to consider with this, and that relates to [making your own models](New-
Models): Foreign relationships to typeclasses are cached by Django and that means that if you were 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
to change an object in a foreign relationship via some other means than via that relationship, the visible than common in Django. See the [closed issue #1098 and its comments](https://github.com/evennia/evennia/issues/1098) for examples and solutions.
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](https://github.com/evennia/evennia/issues/1098) for examples and solutions.
## Will I run out of dbrefs? ## Will I run out of dbrefs?