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

@ -1,41 +1,39 @@
# Accounts
```
┌──────┐ │ ┌───────┐ ┌───────┐ ┌──────┐
│Client├─┼──►│Session├───►│Account├──►│Object│
└──────┘ │ └───────┘ └───────┘ └──────┘
^
```
All *users* (real people) that starts a game [Session](./Sessions.md) 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](./Objects.md)
(normally a [Character](./Objects.md#characters)).
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.
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)).
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.
Apart from storing login information and other account-specific data, the Account object is what is
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`.
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.
Logged into default evennia, you can use the `ooc` command to leave your current
[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.
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.
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).
> ooc
## 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
principle create a system that changes an account's typeclass dynamically).
> ic
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`).
This re-puppets the latest character.
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:
@ -44,64 +42,49 @@ Here's an example of modifying the default Account class in code:
from evennia import DefaultAccount
class Account(DefaultAccount): # [...]
class Account(DefaultAccount):
# [...]
def 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
```
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 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`:
Reload the server with `reload`.
... 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()] ```
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
Beyond those properties assigned to all typeclassed objects (see [Typeclasses](./Typeclasses.md)), the
Account also has the following custom properties:
Beyond those properties assigned to all typeclassed objects (see [Typeclasses](./Typeclasses.md)), 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](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.
- `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.
- `is_superuser` (bool: True/False) - if this account is a superuser.
Special handlers:
- `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`.
- `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).
- `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).
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.
- `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.
- `search` - search for Accounts.

View file

@ -1,23 +1,53 @@
# Objects
```
┌──────┐ │ ┌───────┐ ┌───────┐ ┌──────┐
│Client├─┼──►│Session├───►│Account├──►│Object│
└──────┘ │ └───────┘ └───────┘ └──────┘
^
```
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](./Typeclasses.md) entities.
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`:
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.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.DefaultExit](evennia.objects.objects.DefaultExit) - an entity that (usually) sits
in a room and represents a one-way connection to another location.
- [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.
You will usually not use the `Default*` parents themselves. In `mygame/typeclasses/` there are
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!
Here are the import paths for the relevant child classes in the game dir
- `mygame.typeclasses.objects.Object` (inherits from `DefaultObject`)
- `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
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.
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.
You can put your new typeclass directly in the relevant parent
module, or you could organize your code in some other way. Here we assume we make a new module
`mygame/typeclasses/flowers.py`:
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`:
```python
# 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
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)
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
`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
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).
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).
For example:
Here is an example:
```python
# in mygame/typeclasses/objects.py
@ -91,139 +116,68 @@ class ObjectParent:
def at_pre_get(self, getter, **kwargs):
# make all entities by default un-pickable
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
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:
- `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` - 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](./Objects.md#exits), it's otherwise usually unset.
- `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.
- `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.
- `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](./Objects.md#exits), it's otherwise usually unset.
- `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.
- `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:
- `cmdset` - this is a handler that stores all [command sets](./Command-Sets.md) defined on the
object (if any).
- `cmdset` - this is a handler that stores all [command sets](./Command-Sets.md) defined on 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
`src/objects/objects.py` for their arguments and more details.
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()` - 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).
- `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.
- `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](./Objects.md#exits) to *and* from this object.
- `clear_contents()` - this will not delete anything, but rather move all contents (except Exits) to
their designated `Home` locations.
- `delete()` - deletes this object, first calling `clear_exits()` and
`clear_contents()`.
- `clear_contents()` - this will not delete anything, but rather move all contents (except Exits) to their designated `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](evennia.objects.objects.DefaultObject).
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).
## 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.
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](./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.
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.
### 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.
*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](./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.
*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.
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.
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.
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](./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 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).
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. In `at_traverse`, `object.move_to(destination)` is triggered. This triggers the following hooks,
in order:
1. In `at_traverse`, `object.move_to(destination)` is triggered. This triggers the following hooks, in order:
1. `obj.at_pre_move(destination)` - if this returns False, move is aborted.
1. `origin.at_pre_leave(obj, destination)`
1. `obj.announce_move_from(destination)`
@ -233,6 +187,4 @@ in order:
1. `obj.at_post_move(source)`
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
and display this as an error message. If this is not found, the Exit will instead call
`at_failed_traverse(obj)` 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.

View file

@ -1,23 +1,19 @@
# Sessions
```
┌──────┐ │ ┌───────┐ ┌───────┐ ┌──────┐
│Client├─┼──►│Session├───►│Account├──►│Object│
└──────┘ │ └───────┘ └───────┘ └──────┘
^
```
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](./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.
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.
> Warning: 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. You have been warned.
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.
## Properties on Sessions
@ -42,41 +38,59 @@ Session statistics are mainly used internally by Evennia.
last time this session was truly visibly active.
- `cmd_total` - Total number of Commands passed through this Session.
## 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:
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.
* `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.
```
┌──────┐ │ ┌───────┐ ┌───────┐ ┌─────────┐
│Client├─┼──►│Session├───►│Account├──►│Character│
└──────┘ │ └───────┘ └───────┘ └─────────┘
```
* `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.
```
┌──────┐ │ ┌───────┐
│Client├─┼──►│Session├──┐
└──────┘ │ └───────┘ └──►┌───────┐ ┌─────────┐
│ │Account├──►│Character│
┌──────┐ │ ┌───────┐ ┌──►└───────┘ └─────────┘
│Client├─┼──►│Session├──┘
└──────┘ │ └───────┘
```
> Note that even if multiple Sessions puppet one Character, there is only ever one instance of that
Character.
* `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.
```
│ ┌───────┐
┌──────┐ │ ┌───────┐ │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

View file

@ -4,10 +4,7 @@
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](./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".
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".
This is how the inheritance looks for the typeclasses in Evennia:
@ -66,12 +63,7 @@ default library):
pass
```
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::
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:
```python
def __init__(self, **kwargs):
@ -86,8 +78,7 @@ 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:
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:
```python
from evennia import DefaultObject
@ -247,8 +238,7 @@ you will always query all children on the database model.
## 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.
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](./Attributes.md), [Tags](./Tags.md) etc, are
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):
```
@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]
```
@ -290,20 +280,15 @@ 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:
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
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.
- `/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:
@ -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)
```
The arguments to this method are described [in the API docs
here](github:evennia.typeclasses.models#typedobjectswap_typeclass).
The arguments to this method are described [in the API docs here](github:evennia.typeclasses.models#typedobjectswap_typeclass).
## How typeclasses actually work
*This is considered an advanced section.*
Technically, typeclasses are [Django proxy
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.
Technically, typeclasses are [Django proxy 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.
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.
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.
## 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.
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](New-
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](https://github.com/evennia/evennia/issues/1098) for examples and solutions.
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](https://github.com/evennia/evennia/issues/1098) for examples and solutions.
## Will I run out of dbrefs?