From fd348593b2a6f03fccde2a7f2836a3a9e1eaab84 Mon Sep 17 00:00:00 2001
From: Evennia docbuilder action
If you have implemented your own tests for your game you can run them from your game dir with
evennia test --settings settings.py .
@@ -156,8 +158,8 @@ forces Evennia to use this settings file over the default one.
Evennia’s test suite makes use of Django unit test system, which in turn relies on Python’s unittest module.
To make the test runner find the tests, they must be put in a module named You might also want to read the Python documentation for the unittest module.test*.py (so test.py,
@@ -221,15 +223,14 @@ and want to test a function in
Evennia offers many custom testing classes that helps with testing Evennia features.
They are all found in evennia.utils.test_resources. Note that
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.
These all use whatever setting you pass to them and works well for testing code in your game dir.
EvenniaTest - this sets up a full object environment for your test. All the created entities
@@ -301,7 +302,7 @@ has a lot of arguments for mimicing different ways of calling a Command, so make
read the API docs for .call().
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
any settings changes in your game dir.
A special case is if you were to create a contribution to go to the evennia/contrib folder that
uses its own database models. The problem with this is that Evennia (and Django) will
only recognize models in settings.INSTALLED_APPS. If a user wants to use your contrib, they will
@@ -383,7 +384,7 @@ of the Evennia distribution and its unit tests should be run with all other Even
If you have custom models with a large number of migrations, creating the test database can take a very long time. If you don’t require migrations to run for your tests, you can disable them with the django-test-without-migrations package. To install it, simply:
$ pip install django-test-without-migrations
@@ -401,6 +402,7 @@ django-test-without-migrations package. To install it, simply:
All users (real people) that starts a game Session on Evennia are doing so through an -object called Account. The Account object has no in-game representation, it represents a unique -game account. In order to actually get on the game the Account must puppet an Object -(normally a Character).
+┌──────┐ │ ┌───────┐ ┌───────┐ ┌──────┐
+│Client├─┼──►│Session├───►│Account├──►│Object│
+└──────┘ │ └───────┘ └───────┘ └──────┘
+ ^
+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 (normally a Character).
Exactly how many Sessions can interact with an Account and its Puppets at once is determined by Evennia’s MULTISESSION_MODE setting.
-Apart from storing login information and other account-specific data, the Account object is what is
-chatting on Channels. It is also a good place to store Permissions to be
-consistent between different in-game characters as well as configuration options. The Account
-object also has its own CmdSet, the AccountCmdSet.
Logged into default evennia, you can use the ooc command to leave your current
-character and go into OOC mode. You are quite limited in this mode, basically it works
-like a simple chat program. It acts as a staging area for switching between Characters (if your
-game supports that) or as a safety mode if your Character gets deleted. Use ic to attempt to
-(re)puppet a Character.
Note that the Account object can have, and often does have, a different set of
-Permissions from the Character they control. Normally you should put your
-permissions on the Account level - this will overrule permissions set on the Character level. For
-the permissions of the Character to come into play the default quell command can be used. This
-allows for exploring the game using a different permission set (but you can’t escalate your
-permissions this way - for hierarchical permissions like Builder, Admin etc, the lower of the
-permissions on the Character/Account will always be used).
You will usually not want more than one Account typeclass for all new accounts (but you could in -principle create a system that changes an account’s typeclass dynamically).
-An Evennia Account is, per definition, a Python class that includes evennia.DefaultAccount among
-its parents. In mygame/typeclasses/accounts.py there is an empty class ready for you to modify.
-Evennia defaults to using this (it inherits directly from DefaultAccount).
Apart from storing login information and other account-specific data, the Account object is what is chatting on Evennia’s default Channels. It is also a good place to store Permissions to be consistent between different in-game characters. It can also hold player-level configuration options.
+The Account object has its own default CmdSet, 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.
> ooc
+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.
> ic
+This re-puppets the latest character.
+Note that the Account object can have, and often does have, a different set of Permissions from the Character they control. Normally you should put your permissions on the Account level - this will overrule permissions set on the Character level. For the permissions of the Character to come into play the default quell command can be used. This allows for exploring the game using a different permission set (but you can’t escalate your permissions this way - for hierarchical permissions like Builder, Admin etc, the lower of the permissions on the Character/Account will always be used).
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:
# in mygame/typeclasses/accounts.py
from evennia import DefaultAccount
- class Account(DefaultAccount): # [...]
-
- at_account_creation(self): "this is called only once, when account is first created"
- self.db.real_name = None # this is set later self.db.real_address = None # "
- self.db.config_1 = True # default config self.db.config_2 = False # "
- self.db.config_3 = 1 # "
-
- # ... whatever else our game needs to know ``` Reload the server with `reload`.
-
+ 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
… 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_TYPECLASSto your settings file, and give the python -path to your custom class as its value. By default this points totypeclasses.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_TYPECLASSto your settings file, and give the python path to your custom class as its value. By default this points totypeclasses.accounts.Account, the empty template we used above.Properties on Accounts¶
-Beyond those properties assigned to all typeclassed objects (see Typeclasses), the -Account also has the following custom properties:
+Beyond those properties assigned to all typeclassed objects (see Typeclasses), the Account also has the following custom properties:
user- a unique link to aUserDjango object, representing the logged-in user.
obj- an alias forcharacter.- -
name- an alias foruser.username- +
sessions- an instance of -ObjectSessionHandler -managing all connected Sessions (physical connections) this object listens to (Note: In older -versions of Evennia, this was a list). The so-calledsession-id(used in many places) is found -as -a propertysessidon each Session instance.
sessions- an instance of ObjectSessionHandler managing all connected Sessions (physical connections) this object listens to (Note: In older versions of Evennia, this was a list). The so-calledsession-id(used in many places) is found as a propertysessidon each Session instance.
is_superuser(bool: True/False) - if this account is a superuser.Special handlers:
- -
cmdset- This holds all the current Commands of this Account. By default these are the commands found in the cmdset defined bysettings.CMDSET_ACCOUNT.- +
nicks- This stores and handles Nicks, in the same way as nicks it works on Objects. -For Accounts, nicks are primarily used to store custom aliases for -Channels.
nicks- This stores and handles Nicks, in the same way as nicks it works on Objects. For Accounts, nicks are primarily used to store custom aliases for Channels.Selection of special methods (see
evennia.DefaultAccountfor 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.- diff --git a/docs/1.0-dev/Components/Components-Overview.html b/docs/1.0-dev/Components/Components-Overview.html index 5662077ca0..957783c679 100644 --- a/docs/1.0-dev/Components/Components-Overview.html +++ b/docs/1.0-dev/Components/Components-Overview.html @@ -137,7 +137,7 @@
msg- send text to the Account- Accounts
diff --git a/docs/1.0-dev/Components/Objects.html b/docs/1.0-dev/Components/Objects.html index 10aa9acc57..a81eec586a 100644 --- a/docs/1.0-dev/Components/Objects.html +++ b/docs/1.0-dev/Components/Objects.html @@ -114,22 +114,50 @@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 entities.
-An Evennia Object is, by definition, a Python class that includes -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 among its parents. Evennia defines several subclasses of
+DefaultObjectin the following inheritance tree:++┌────────────┐ + 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
+DefaultObjetis a child ofDefaultCharacter(in the Evennia library), which is a parent ofCharacter(in the game dir). The class in the game-dir is the one you should modify for your game.+Note the
+ObjectParentclass. 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 - -the normal in-game Character, controlled by a player.
evennia.objects.objects.DefaultCharacter - the normal in-game Character, controlled by a player.
- -
evennia.objects.objects.DefaultRoom - a location in the game world.
- +
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 - 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. Inmygame/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 fromDefaultObject)- @@ -138,11 +166,8 @@ the defaults. Tweaking them is one of the main ways to customize you game!
mygame.typeclasses.characters.Character(inherits fromDefaultCharacter)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 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 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 module, or you could organize your code in some other way. Here we assume we make a new module
mygame/typeclasses/flowers.py:# mygame/typeclasses/flowers.py from typeclasses.objects import Object @@ -160,7 +185,7 @@ module, or you could organize your code in some other way. Here we assume we makNow you just need to point to the class Rose with the
-createcommand to make a new rose:@create/drop MyRose:flowers.Rose +create/drop MyRose:flowers.RoseWhat the
+createcommand actually does is to use the evennia.create_object @@ -181,10 +206,8 @@ will usually want to change this at build time (using theAdding common functionality¶
-
Object,Character,RoomandExitalso inherit frommygame.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 for details).For example:
+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 for details).Here is an example:
+# in mygame/typeclasses/objects.py # ... @@ -194,137 +217,65 @@ docs on 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.RoomandCharacterdefault to not being able to be picked up using thegetcommand.Properties and functions on Objects¶
Beyond the properties assigned to all typeclassed objects (see that page for a list of those), the Object also has the following custom properties:
-
- +
aliases- a handler that allows you to add and remove aliases from this object. Use -aliases.add()to add a new alias andaliases.remove()to remove one.
aliases- a handler that allows you to add and remove aliases from this object. Usealiases.add()to add a new alias andaliases.remove()to remove one.- -
location- a reference to the object currently containing this object.- -
homeis a backup location. The main motivation is to have a safe place to move the object to if -itslocationis destroyed. All objects should usually have a home location for safety.- -
destination- this holds a reference to another object this object links to in some way. Its -main use is for Exits, it’s otherwise usually unset.- -
nicks- as opposed to aliases, a Nick holds a convenient nickname replacement for a -real name, word or sequence, only valid for this object. This mainly makes sense if the Object is -used as a game character - it can then store briefer shorts, example so as to quickly reference game -commands or other characters. Use nicks.add(alias, realname) to add a new one.- -
account- this holds a reference to a connected Account controlling this object (if -any). Note that this is set also if the controlling account is not currently online - to test if -an account is online, use thehas_accountproperty instead.- -
sessions- ifaccountfield 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 theirlocation).- +
exits- this returns all objects inside this object that are Exits, that is, has the -destinationproperty set.- +
homeis a backup location. The main motivation is to have a safe place to move the object to if itslocationis destroyed. All objects should usually have a home location for safety.- +
destination- this holds a reference to another object this object links to in some way. Its main use is for Exits, it’s otherwise usually unset.- +
nicks- as opposed to aliases, a Nick holds a convenient nickname replacement for a real name, word or sequence, only valid for this object. This mainly makes sense if the Object is used as a game character - it can then store briefer shorts, example so as to quickly reference game commands or other characters. Use nicks.add(alias, realname) to add a new one.- +
account- this holds a reference to a connected Account controlling this object (if any). Note that this is set also if the controlling account is not currently online - to test if an account is online, use thehas_accountproperty instead.- +
sessions- ifaccountfield 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 theirlocation).
exits- this returns all objects inside this object that are Exits, that is, has thedestinationproperty set.The last two properties are special:
-
-- +
cmdset- this is a handler that stores all command sets defined on the -object (if any).
cmdset- this is a handler that stores all command sets defined on the object (if any).
scripts- this is a handler that manages Scripts attached to the object (if any).The Object also has a host of useful utility functions. See the function headers in -
+src/objects/objects.pyfor their arguments and more details.The Object also has a host of useful utility functions. See the function headers in
src/objects/objects.pyfor 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()- callsmsgon 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 namedcallerand one can docaller.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 namedcallerand one can docaller.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 to and from this object.- -
clear_contents()- this will not delete anything, but rather move all contents (except Exits) to -their designatedHomelocations.- +
delete()- deletes this object, first callingclear_exits()and -clear_contents().- +
clear_contents()- this will not delete anything, but rather move all contents (except Exits) to their designatedHomelocations.
delete()- deletes this object, first callingclear_exits()andclear_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. Seeevennia.objects.objectsfor an -updated list of all the available hooks or the API for DefaultObject here.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. Seeevennia.objects.objectsfor an updated list of all the available hooks or the API for DefaultObject here.diff --git a/docs/1.0-dev/Components/Sessions.html b/docs/1.0-dev/Components/Sessions.html index 4bd7cc7c3b..d23ff1f9bd 100644 --- a/docs/1.0-dev/Components/Sessions.html +++ b/docs/1.0-dev/Components/Sessions.html @@ -111,23 +111,17 @@ 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. When a new Account -logs in to Evennia for the first time, a new
+Characterobject is created and -the Account object is assigned to theaccountattribute. ACharacterobject -must have a Default Commandset set on itself at -creation, or the account will not be able to issue any commands! If you just -inherit your own class fromevennia.DefaultCharacterand make sure to use -super()to call the parent methods you should be fine. In -mygame/typeclasses/characters.pyis an emptyCharacterclass ready for you -to modify.Characters are objects controlled by Accounts. When a new Account logs in to Evennia for the first time, a new
Characterobject is created and the Account object is assigned to theaccountattribute. ACharacterobject must have a Default Commandset set on itself at creation, or the account will not be able to issue any commands! If you just inherit your own class fromevennia.DefaultCharacterand make sure to usesuper()to call the parent methods you should be fine. Inmygame/typeclasses/characters.pyis an emptyCharacterclass 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
+locationof 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 fromev.DefaultRoom. Inmygame/typeclasses/rooms.pyis an emptyRoomclass 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
locationof their own and that default commands like@digcreates objects of this class - so if you want to expand your rooms with more functionality, just inherit fromev.DefaultRoom. Inmygame/typeclasses/rooms.pyis an emptyRoomclass ready for you to modify.Exits¶
-Exits are objects connecting other objects (usually Rooms) together. An object named North or -in might be an exit, as well as door, portal or jump out the window. An exit has two things -that separate them from other objects. Firstly, their destination property is set and points to a -valid object. This fact makes it easy and fast to locate exits in the database. Secondly, exits -define a special Transit Command on themselves when they are created. This command is -named the same as the exit object and will, when called, handle the practicalities of moving the -character to the Exits’s destination - this allows you to just enter the name of the exit on its -own to move around, just as you would expect.
-The exit functionality is all defined on the Exit typeclass, so you could in principle completely -change how exits work in your game (it’s not recommended though, unless you really know what you are -doing). Exits are locked using an access_type called traverse and also make use of a few -hook methods for giving feedback if the traversal fails. See
+evennia.DefaultExitfor more info. -Inmygame/typeclasses/exits.pythere is an emptyExitclass for you to modify.Exits are objects connecting other objects (usually Rooms) together. An object named North or in might be an exit, as well as door, portal or jump out the window. An exit has two things that separate them from other objects. Firstly, their destination property is set and points to a valid object. This fact makes it easy and fast to locate exits in the database. Secondly, exits define a special Transit Command on themselves when they are created. This command is named the same as the exit object and will, when called, handle the practicalities of moving the character to the Exits’s destination - this allows you to just enter the name of the exit on its own to move around, just as you would expect.
+The exit functionality is all defined on the Exit typeclass, so you could in principle completely change how exits work in your game (it’s not recommended though, unless you really know what you are doing). Exits are locked using an access_type called traverse and also make use of a few hook methods for giving feedback if the traversal fails. See
evennia.DefaultExitfor more info. Inmygame/typeclasses/exits.pythere is an emptyExitclass for you to modify.The process of traversing an exit is as follows:
-
-- +
The traversing
objsends a command that matches the Exit-command name on the Exit object. The -cmdhandler detects this and triggers the command defined on the Exit. Traversal always -involves the “source” (the current location) and thedestination(this is stored on the Exit -object).The traversing
objsends a command that matches the Exit-command name on the Exit object. The cmdhandler detects this and triggers the command defined on the Exit. Traversal always involves the “source” (the current location) and thedestination(this is stored on the Exit object).The Exit command checks the
traverselock on the Exit object- -
The Exit command triggers
at_traverse(obj, destination)on the Exit object.In
+at_traverse,object.move_to(destination)is triggered. This triggers the following hooks, -in order:In
at_traverse,object.move_to(destination)is triggered. This triggers the following hooks, in order:
obj.at_pre_move(destination)- if this returns False, move is aborted.- @@ -337,9 +288,7 @@ in order:
origin.at_pre_leave(obj, destination)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_traverseon 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_traverseon itself and display this as an error message. If this is not found, the Exit will instead callat_failed_traverse(obj)on itself.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, usually the “unloggedin” cmdset. This is what -is used to show the login screen and to handle commands to create a new account (or -Account in evennia lingo) read initial help and to log into the game with an existing -account. A session object can either be “logged in” or not. Logged in means that the user has -authenticated. When this happens the session is associated with an Account object (which is what -holds account-centric stuff). The account can then in turn puppet any number of objects/characters.
--+Warning: A Session is not persistent - it is not a Typeclass and has no -connection to the database. The Session will go away when a user disconnects and you will lose any -custom data on it if the server reloads. The
-.dbhandler on Sessions is there to present a uniform -API (so you can assume.dbexists 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 object has its own cmdset, usually the “unloggedin” cmdset. This is what is used to show the login screen and to handle commands to create a new account (or Account in evennia lingo) read initial help and to log into the game with an existing account. A session object can either be “logged in” or not. Logged in means that the user has authenticated. When this happens the session is associated with an Account object (which is what holds account-centric stuff). The account can then in turn puppet any number of objects/characters.
+A Session is not persistent - it is not a Typeclass and has no connection to the database. The Session will go away when a user disconnects and you will lose any custom data on it if the server reloads. The
.dbhandler on Sessions is there to present a uniform API (so you can assume.dbexists 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¶
Here are some important properties available on (Server-)Sessions
@@ -154,39 +148,61 @@ last time this session was truly visibly active.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_MODEsetting:-
- -
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 thecreate accountCommand 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 alook) 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’sOOCLookcommand 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.The number of sessions possible to connect to a given account at the same time and how it works is given by the
+MULTISESSION_MODEsetting:+
- +
+
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├──┘ +└──────┘ │ └───────┘ + │ +- +
+
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 alook) 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.
+Note that even if multiple Sessions puppet one Character, there is only ever one instance of that Character.
diff --git a/docs/1.0-dev/Components/Typeclasses.html b/docs/1.0-dev/Components/Typeclasses.html index 7c39b46a28..9e2b1479c9 100644 --- a/docs/1.0-dev/Components/Typeclasses.html +++ b/docs/1.0-dev/Components/Typeclasses.html @@ -118,10 +118,7 @@ Typeclasses form the core of Evennia’s data storage. It allows Evennia to represent any number of different game entities as Python classes, without having to modify the database schema for every new type.
-In Evennia the most important game entities, Accounts, Objects, -Scripts and Channels are all Python classes inheriting, at -varying distance, from
+evennia.typeclasses.models.TypedObject. In the documentation we refer to -these objects as being “typeclassed” or even “being a typeclass”.In Evennia the most important game entities, Accounts, Objects, Scripts and Channels are all Python classes inheriting, at varying distance, from
evennia.typeclasses.models.TypedObject. In the documentation we refer to these objects as being “typeclassed” or even “being a typeclass”.This is how the inheritance looks for the typeclasses in Evennia:
-TypedObject _________________|_________________________________ @@ -175,12 +172,7 @@ default library):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 -theat_*_creationhooks (likeat_object_creationfor Objects) for setting things the very first -time the typeclass is saved to the database or theat_inithook 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 usesuperto call its parent::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 theat_*_creationhooks (likeat_object_creationfor Objects) for setting things the very first time the typeclass is saved to the database or theat_inithook 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 usesuperto call its parent:def __init__(self, **kwargs): # my content super().__init__(**kwargs) @@ -194,8 +186,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:
from evennia import DefaultObject class Furniture(DefaultObject): @@ -334,8 +325,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
@@ -361,7 +351,7 @@ it all in-game usingdb_*fields, Attributes, Tags etc, are not themselves embedded into the class and will not be updated automatically. This you need to manage yourself, by searching for all relevant objects and updating or adding the data:;and list comprehensions, like this (ignore the line break, that’s only for readability in the wiki): -@py from typeclasses.furniture import Furniture; +@@ -370,59 +360,35 @@ retroactively update objects more than necessary.py from typeclasses.furniture import Furniture; [obj.at_object_creation() for obj in Furniture.objects.all() if not obj.db.worth]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
-@typeclasscommand:@typeclass objname = path.to.new.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
+@typeclasscommand:typeclass objname = path.to.new.typeclassThere are two important switches to this command:
-
- -
/reset- This will purge all existing Attributes on the object and re-run the creation hook -(likeat_object_creationfor 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 (likeat_object_creationfor 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/resetto re-run the creation hook on an existing class.In code you instead use the
swap_typeclassmethod which you can find on all typeclassed entities:-obj_to_change.swap_typeclass(new_typeclass_path, clean_attributes=False, run_start_hooks="all", no_default=True, clean_cmdsets=False)The arguments to this method are described in the API docs -here.
+The arguments to this method are described in the API docs here.
How typeclasses actually work¶
This is considered an advanced section.
-Technically, typeclasses are Django proxy -models. The only database -models that are “real” in the typeclass system (that is, are represented by actual tables in the -database) are
-AccountDB,ObjectDB,ScriptDBandChannelDB(there are also -Attributes and Tags but they are not typeclasses themselves). All the -subclasses of them are “proxies”, extending them with Python code without actually modifying the -database layout.Evennia modifies Django’s proxy model in various ways to allow them to work without any boiler plate -(for example you don’t need to set the Django “proxy” property in the model
+Metasubclass, 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.Technically, typeclasses are Django proxy models. The only database +models that are “real” in the typeclass system (that is, are represented by actual tables in the database) are
+AccountDB,ObjectDB,ScriptDBandChannelDB(there are also Attributes and Tags but they are not typeclasses themselves). All the subclasses of them are “proxies”, extending them with Python code without actually modifying the database layout.Evennia modifies Django’s proxy model in various ways to allow them to work without any boiler plate (for example you don’t need to set the Django “proxy” property in the model
Metasubclass, 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 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 for examples and solutions.Will I run out of dbrefs?¶
diff --git a/docs/1.0-dev/_sources/Coding/Unit-Testing.md.txt b/docs/1.0-dev/_sources/Coding/Unit-Testing.md.txt index 5de3625b54..833ac5d3b0 100644 --- a/docs/1.0-dev/_sources/Coding/Unit-Testing.md.txt +++ b/docs/1.0-dev/_sources/Coding/Unit-Testing.md.txt @@ -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 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 with @@ -46,7 +46,7 @@ You can also test specific things by giving their path 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 *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). -## Using the Evennia testing classes +### Using the Evennia testing classes 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 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. -### 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. @@ -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 [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 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 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 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 django-test-without-migrations package. To install it, simply: diff --git a/docs/1.0-dev/_sources/Components/Accounts.md.txt b/docs/1.0-dev/_sources/Components/Accounts.md.txt index d2bd11c269..02865b38e2 100644 --- a/docs/1.0-dev/_sources/Components/Accounts.md.txt +++ b/docs/1.0-dev/_sources/Components/Accounts.md.txt @@ -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. \ No newline at end of file diff --git a/docs/1.0-dev/_sources/Components/Objects.md.txt b/docs/1.0-dev/_sources/Components/Objects.md.txt index 7fb6583ee2..3a030ca03f 100644 --- a/docs/1.0-dev/_sources/Components/Objects.md.txt +++ b/docs/1.0-dev/_sources/Components/Objects.md.txt @@ -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. \ No newline at end of file +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. \ No newline at end of file diff --git a/docs/1.0-dev/_sources/Components/Sessions.md.txt b/docs/1.0-dev/_sources/Components/Sessions.md.txt index 159612bce2..c9b931269b 100644 --- a/docs/1.0-dev/_sources/Components/Sessions.md.txt +++ b/docs/1.0-dev/_sources/Components/Sessions.md.txt @@ -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 diff --git a/docs/1.0-dev/_sources/Components/Typeclasses.md.txt b/docs/1.0-dev/_sources/Components/Typeclasses.md.txt index 073bcd0803..6a3d4fb2ad 100644 --- a/docs/1.0-dev/_sources/Components/Typeclasses.md.txt +++ b/docs/1.0-dev/_sources/Components/Typeclasses.md.txt @@ -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? diff --git a/docs/1.0-dev/api/evennia.commands.default.account.html b/docs/1.0-dev/api/evennia.commands.default.account.html index b8c1c7e5ff..e271df0c7e 100644 --- a/docs/1.0-dev/api/evennia.commands.default.account.html +++ b/docs/1.0-dev/api/evennia.commands.default.account.html @@ -133,7 +133,7 @@ method. Otherwise all text will be returned to all connected sessions.@@ -164,7 +164,7 @@ method. Otherwise all text will be returned to all connected sessions.
diff --git a/docs/1.0-dev/api/evennia.commands.default.admin.html b/docs/1.0-dev/api/evennia.commands.default.admin.html index 40627cc099..e83e49fa66 100644 --- a/docs/1.0-dev/api/evennia.commands.default.admin.html +++ b/docs/1.0-dev/api/evennia.commands.default.admin.html @@ -317,7 +317,7 @@ to accounts respectively.
- -
+search_index_entry= {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\n look while out-of-character\n\n Usage:\n look\n\n Look in the ooc state.\n '}¶search_index_entry= {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\n look while out-of-character\n\n Usage:\n look\n\n Look in the ooc state.\n '}¶@@ -348,7 +348,7 @@ to accounts respectively.
diff --git a/docs/1.0-dev/api/evennia.commands.default.building.html b/docs/1.0-dev/api/evennia.commands.default.building.html index e45fb68806..8fbdcc7640 100644 --- a/docs/1.0-dev/api/evennia.commands.default.building.html +++ b/docs/1.0-dev/api/evennia.commands.default.building.html @@ -1345,7 +1345,7 @@ server settings.
- -
+search_index_entry= {'aliases': 'remit pemit', 'category': 'admin', 'key': 'emit', 'no_prefix': ' remit pemit', 'tags': '', 'text': '\n admin command for emitting message to multiple objects\n\n Usage:\n emit[/switches] [<obj>, <obj>, ... =] <message>\n remit [<obj>, <obj>, ... =] <message>\n pemit [<obj>, <obj>, ... =] <message>\n\n Switches:\n room - limit emits to rooms only (default)\n accounts - limit emits to accounts only\n contents - send to the contents of matched objects too\n\n Emits a message to the selected objects or to\n your immediate surroundings. If the object is a room,\n send to its contents. remit and pemit are just\n limited forms of emit, for sending to rooms and\n to accounts respectively.\n '}¶search_index_entry= {'aliases': 'pemit remit', 'category': 'admin', 'key': 'emit', 'no_prefix': ' pemit remit', 'tags': '', 'text': '\n admin command for emitting message to multiple objects\n\n Usage:\n emit[/switches] [<obj>, <obj>, ... =] <message>\n remit [<obj>, <obj>, ... =] <message>\n pemit [<obj>, <obj>, ... =] <message>\n\n Switches:\n room - limit emits to rooms only (default)\n accounts - limit emits to accounts only\n contents - send to the contents of matched objects too\n\n Emits a message to the selected objects or to\n your immediate surroundings. If the object is a room,\n send to its contents. remit and pemit are just\n limited forms of emit, for sending to rooms and\n to accounts respectively.\n '}¶
- -
+aliases= ['@typeclasses', '@swap', '@type', '@parent', '@update']¶aliases= ['@parent', '@swap', '@update', '@type', '@typeclasses']¶@@ -1376,7 +1376,7 @@ server settings.
@@ -1531,7 +1531,7 @@ If object is not specified, the current location is examined.
- -
+search_index_entry= {'aliases': '@typeclasses @swap @type @parent @update', 'category': 'building', 'key': '@typeclass', 'no_prefix': 'typeclass typeclasses swap type parent update', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclasses or typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object. This will also\n reset cmdsets!\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}¶search_index_entry= {'aliases': '@parent @swap @update @type @typeclasses', 'category': 'building', 'key': '@typeclass', 'no_prefix': 'typeclass parent swap update type typeclasses', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclasses or typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object. This will also\n reset cmdsets!\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}¶@@ -1799,7 +1799,7 @@ the cases, see the module doc.
@@ -1833,7 +1833,7 @@ one is given.
- -
+search_index_entry= {'aliases': '@ex @exam', 'category': 'building', 'key': '@examine', 'no_prefix': 'examine ex exam', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n script - examine a Script\n channel - examine a Channel\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}¶search_index_entry= {'aliases': '@exam @ex', 'category': 'building', 'key': '@examine', 'no_prefix': 'examine exam ex', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n script - examine a Script\n channel - examine a Channel\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}¶@@ -1864,7 +1864,7 @@ one is given.
diff --git a/docs/1.0-dev/api/evennia.commands.default.comms.html b/docs/1.0-dev/api/evennia.commands.default.comms.html index c2f746c095..c78ef6328f 100644 --- a/docs/1.0-dev/api/evennia.commands.default.comms.html +++ b/docs/1.0-dev/api/evennia.commands.default.comms.html @@ -256,7 +256,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.
- -
+search_index_entry= {'aliases': '@search @locate', 'category': 'building', 'key': '@find', 'no_prefix': 'find search locate', 'tags': '', 'text': '\n search the database for objects\n\n Usage:\n find[/switches] <name or dbref or *account> [= dbrefmin[-dbrefmax]]\n locate - this is a shorthand for using the /loc switch.\n\n Switches:\n room - only look for rooms (location=None)\n exit - only look for exits (destination!=None)\n char - only look for characters (BASE_CHARACTER_TYPECLASS)\n exact - only exact matches are returned.\n loc - display object location if exists and match has one result\n startswith - search for names starting with the string, rather than containing\n\n Searches the database for an object of a particular name or exact #dbref.\n Use *accountname to search for an account. The switches allows for\n limiting object matches to certain game entities. Dbrefmin and dbrefmax\n limits matches to within the given dbrefs range, or above/below if only\n one is given.\n '}¶search_index_entry= {'aliases': '@locate @search', 'category': 'building', 'key': '@find', 'no_prefix': 'find locate search', 'tags': '', 'text': '\n search the database for objects\n\n Usage:\n find[/switches] <name or dbref or *account> [= dbrefmin[-dbrefmax]]\n locate - this is a shorthand for using the /loc switch.\n\n Switches:\n room - only look for rooms (location=None)\n exit - only look for exits (destination!=None)\n char - only look for characters (BASE_CHARACTER_TYPECLASS)\n exact - only exact matches are returned.\n loc - display object location if exists and match has one result\n startswith - search for names starting with the string, rather than containing\n\n Searches the database for an object of a particular name or exact #dbref.\n Use *accountname to search for an account. The switches allows for\n limiting object matches to certain game entities. Dbrefmin and dbrefmax\n limits matches to within the given dbrefs range, or above/below if only\n one is given.\n '}¶@@ -782,7 +782,7 @@ don’t actually sub to yet.
@@ -935,7 +935,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.
- -
+search_index_entry= {'aliases': '@channels @chan', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel channels chan', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}¶search_index_entry= {'aliases': '@chan @channels', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel chan channels', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}¶@@ -955,7 +955,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.
diff --git a/docs/1.0-dev/api/evennia.commands.default.general.html b/docs/1.0-dev/api/evennia.commands.default.general.html index 6c04b1b56b..77b84da2a8 100644 --- a/docs/1.0-dev/api/evennia.commands.default.general.html +++ b/docs/1.0-dev/api/evennia.commands.default.general.html @@ -175,7 +175,7 @@ look *<account&g
- -
+search_index_entry= {'aliases': '@channels @chan', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel channels chan', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}¶search_index_entry= {'aliases': '@chan @channels', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel chan channels', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}¶@@ -206,7 +206,7 @@ look *<account&g
@@ -268,7 +268,7 @@ for everyone to use, you need build privileges and the alias command.
- -
+search_index_entry= {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\n look at location or object\n\n Usage:\n look\n look <obj>\n look *<account>\n\n Observes your location or objects in your vicinity.\n '}¶search_index_entry= {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\n look at location or object\n\n Usage:\n look\n look <obj>\n look *<account>\n\n Observes your location or objects in your vicinity.\n '}¶@@ -300,7 +300,7 @@ for everyone to use, you need build privileges and the alias command.
@@ -323,7 +323,7 @@ inv
- -
+search_index_entry= {'aliases': 'nicks nickname', 'category': 'general', 'key': 'nick', 'no_prefix': ' nicks nickname', 'tags': '', 'text': '\n define a personal alias/nick by defining a string to\n match and replace it with another on the fly\n\n Usage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\n Switches:\n inputline - replace on the inputline (default)\n object - replace on object-lookup\n account - replace on account-lookup\n list - show all defined aliases (also "nicks" works)\n delete - remove nick by index in /list\n clearall - clear all nicks\n\n Examples:\n nick hi = say Hello, I\'m Sarah!\n nick/object tom = the tall man\n nick build $1 $2 = create/drop $1;$2\n nick tell $1 $2=page $1=$2\n nick tm?$1=page tallman=$1\n nick tm\\=$1=page tallman=$1\n\n A \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\n Put the last $-marker without an ending space to catch all remaining text. You\n can also use unix-glob matching for the left-hand side <string>:\n\n * - matches everything\n ? - matches 0 or 1 single characters\n [abcd] - matches these chars in any order\n [!abcd] - matches everything not among these chars\n \\= - escape literal \'=\' you want in your <string>\n\n Note that no objects are actually renamed or changed by this command - your nicks\n are only available to you. If you want to permanently add keywords to an object\n for everyone to use, you need build privileges and the alias command.\n\n '}¶search_index_entry= {'aliases': 'nickname nicks', 'category': 'general', 'key': 'nick', 'no_prefix': ' nickname nicks', 'tags': '', 'text': '\n define a personal alias/nick by defining a string to\n match and replace it with another on the fly\n\n Usage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\n Switches:\n inputline - replace on the inputline (default)\n object - replace on object-lookup\n account - replace on account-lookup\n list - show all defined aliases (also "nicks" works)\n delete - remove nick by index in /list\n clearall - clear all nicks\n\n Examples:\n nick hi = say Hello, I\'m Sarah!\n nick/object tom = the tall man\n nick build $1 $2 = create/drop $1;$2\n nick tell $1 $2=page $1=$2\n nick tm?$1=page tallman=$1\n nick tm\\=$1=page tallman=$1\n\n A \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\n Put the last $-marker without an ending space to catch all remaining text. You\n can also use unix-glob matching for the left-hand side <string>:\n\n * - matches everything\n ? - matches 0 or 1 single characters\n [abcd] - matches these chars in any order\n [!abcd] - matches everything not among these chars\n \\= - escape literal \'=\' you want in your <string>\n\n Note that no objects are actually renamed or changed by this command - your nicks\n are only available to you. If you want to permanently add keywords to an object\n for everyone to use, you need build privileges and the alias command.\n\n '}¶@@ -354,7 +354,7 @@ inv
@@ -773,7 +773,7 @@ which permission groups you are a member of.
- -
+search_index_entry= {'aliases': 'inv i', 'category': 'general', 'key': 'inventory', 'no_prefix': ' inv i', 'tags': '', 'text': '\n view inventory\n\n Usage:\n inventory\n inv\n\n Shows your inventory.\n '}¶search_index_entry= {'aliases': 'i inv', 'category': 'general', 'key': 'inventory', 'no_prefix': ' i inv', 'tags': '', 'text': '\n view inventory\n\n Usage:\n inventory\n inv\n\n Shows your inventory.\n '}¶@@ -804,7 +804,7 @@ which permission groups you are a member of.
diff --git a/docs/1.0-dev/api/evennia.commands.default.system.html b/docs/1.0-dev/api/evennia.commands.default.system.html index 972851c8a4..e450185933 100644 --- a/docs/1.0-dev/api/evennia.commands.default.system.html +++ b/docs/1.0-dev/api/evennia.commands.default.system.html @@ -683,7 +683,7 @@ See |luhttps://ww
- -
+search_index_entry= {'aliases': 'groups hierarchy', 'category': 'general', 'key': 'access', 'no_prefix': ' groups hierarchy', 'tags': '', 'text': '\n show your current game access\n\n Usage:\n access\n\n This command shows you the permission hierarchy and\n which permission groups you are a member of.\n '}¶search_index_entry= {'aliases': 'hierarchy groups', 'category': 'general', 'key': 'access', 'no_prefix': ' hierarchy groups', 'tags': '', 'text': '\n show your current game access\n\n Usage:\n access\n\n This command shows you the permission hierarchy and\n which permission groups you are a member of.\n '}¶@@ -729,7 +729,7 @@ to all the variables defined therein.
diff --git a/docs/1.0-dev/api/evennia.commands.default.tests.html b/docs/1.0-dev/api/evennia.commands.default.tests.html index 270c61fbfd..3780da897b 100644 --- a/docs/1.0-dev/api/evennia.commands.default.tests.html +++ b/docs/1.0-dev/api/evennia.commands.default.tests.html @@ -902,7 +902,7 @@ main test suite started with
- -
+search_index_entry= {'aliases': '@delays @task', 'category': 'system', 'key': '@tasks', 'no_prefix': 'tasks delays task', 'tags': '', 'text': "\n Display or terminate active tasks (delays).\n\n Usage:\n tasks[/switch] [task_id or function_name]\n\n Switches:\n pause - Pause the callback of a task.\n unpause - Process all callbacks made since pause() was called.\n do_task - Execute the task (call its callback).\n call - Call the callback of this task.\n remove - Remove a task without executing it.\n cancel - Stop a task from automatically executing.\n\n Notes:\n A task is a single use method of delaying the call of a function. Calls are created\n in code, using `evennia.utils.delay`.\n See |luhttps://www.evennia.com/docs/latest/Command-Duration.html|ltthe docs|le for help.\n\n By default, tasks that are canceled and never called are cleaned up after one minute.\n\n Examples:\n - `tasks/cancel move_callback` - Cancels all movement delays from the slow_exit contrib.\n In this example slow exits creates it's tasks with\n `utils.delay(move_delay, move_callback)`\n - `tasks/cancel 2` - Cancel task id 2.\n\n "}¶search_index_entry= {'aliases': '@task @delays', 'category': 'system', 'key': '@tasks', 'no_prefix': 'tasks task delays', 'tags': '', 'text': "\n Display or terminate active tasks (delays).\n\n Usage:\n tasks[/switch] [task_id or function_name]\n\n Switches:\n pause - Pause the callback of a task.\n unpause - Process all callbacks made since pause() was called.\n do_task - Execute the task (call its callback).\n call - Call the callback of this task.\n remove - Remove a task without executing it.\n cancel - Stop a task from automatically executing.\n\n Notes:\n A task is a single use method of delaying the call of a function. Calls are created\n in code, using `evennia.utils.delay`.\n See |luhttps://www.evennia.com/docs/latest/Command-Duration.html|ltthe docs|le for help.\n\n By default, tasks that are canceled and never called are cleaned up after one minute.\n\n Examples:\n - `tasks/cancel move_callback` - Cancels all movement delays from the slow_exit contrib.\n In this example slow exits creates it's tasks with\n `utils.delay(move_delay, move_callback)`\n - `tasks/cancel 2` - Cancel task id 2.\n\n "}¶Test the batch processor.
+
red_button= <module 'evennia.contrib.tutorials.red_button.red_button' from '/tmp/tmpm73bft6s/90ed54066824f123e4c0861d1b23071397daf8ab/evennia/contrib/tutorials/red_button/red_button.py'>¶diff --git a/docs/1.0-dev/api/evennia.commands.default.unloggedin.html b/docs/1.0-dev/api/evennia.commands.default.unloggedin.html index c5a85f4273..369cd0bf21 100644 --- a/docs/1.0-dev/api/evennia.commands.default.unloggedin.html +++ b/docs/1.0-dev/api/evennia.commands.default.unloggedin.html @@ -122,7 +122,7 @@ connect “account name” “pass word”
@@ -157,7 +157,7 @@ there is no object yet before the account has logged in)
@@ -181,7 +181,7 @@ create “account name” “pass word”
- -
+search_index_entry= {'aliases': 'co conn con', 'category': 'general', 'key': 'connect', 'no_prefix': ' co conn con', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶search_index_entry= {'aliases': 'co con conn', 'category': 'general', 'key': 'connect', 'no_prefix': ' co con conn', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶@@ -212,7 +212,7 @@ create “account name” “pass word”
diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html index f732f7f303..60211dbba5 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html @@ -139,7 +139,7 @@ the module given by settings.CONNECTION_SCREEN_MODULE.
- -
+search_index_entry= {'aliases': 'cr cre', 'category': 'general', 'key': 'create', 'no_prefix': ' cr cre', 'tags': '', 'text': '\n create a new account account\n\n Usage (at login screen):\n create <accountname> <password>\n create "account name" "pass word"\n\n This creates a new account account.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶search_index_entry= {'aliases': 'cre cr', 'category': 'general', 'key': 'create', 'no_prefix': ' cre cr', 'tags': '', 'text': '\n create a new account account\n\n Usage (at login screen):\n create <accountname> <password>\n create "account name" "pass word"\n\n This creates a new account account.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶@@ -169,7 +169,7 @@ there is no object yet before the account has logged in)
@@ -191,7 +191,7 @@ there is no object yet before the account has logged in)
- -
+search_index_entry= {'aliases': 'co conn con', 'category': 'general', 'key': 'connect', 'no_prefix': ' co conn con', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}¶search_index_entry= {'aliases': 'co con conn', 'category': 'general', 'key': 'connect', 'no_prefix': ' co con conn', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}¶@@ -227,7 +227,7 @@ name enclosed in quotes:
diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.commands.html b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.commands.html index eab203ff9f..6c2ac9fd1a 100644 --- a/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.commands.html +++ b/docs/1.0-dev/api/evennia.contrib.base_systems.ingame_python.commands.html @@ -116,7 +116,7 @@
- -
+search_index_entry= {'aliases': 'cr cre', 'category': 'general', 'key': 'create', 'no_prefix': ' cr cre', 'tags': '', 'text': '\n Create a new account.\n\n Usage (at login screen):\n create "accountname" <email> <password>\n\n This creates a new account account.\n\n '}¶search_index_entry= {'aliases': 'cre cr', 'category': 'general', 'key': 'create', 'no_prefix': ' cre cr', 'tags': '', 'text': '\n Create a new account.\n\n Usage (at login screen):\n create "accountname" <email> <password>\n\n This creates a new account account.\n\n '}¶@@ -197,7 +197,7 @@ on user permission.
diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html index d25a7254e0..480f2533de 100644 --- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html +++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html @@ -211,7 +211,7 @@ the operation will be general or on the room.
- -
+search_index_entry= {'aliases': '@callbacks @callback @calls', 'category': 'building', 'key': '@call', 'no_prefix': 'call callbacks callback calls', 'tags': '', 'text': '\n Command to edit callbacks.\n '}¶search_index_entry= {'aliases': '@callbacks @calls @callback', 'category': 'building', 'key': '@call', 'no_prefix': 'call callbacks calls callback', 'tags': '', 'text': '\n Command to edit callbacks.\n '}¶@@ -235,7 +235,7 @@ set in self.parse())
@@ -256,7 +256,7 @@ set in self.parse())
- -
+search_index_entry= {'aliases': 'q chicken out abort quit', 'category': 'evscaperoom', 'key': 'give up', 'no_prefix': ' q chicken out abort quit', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}¶search_index_entry= {'aliases': 'quit q abort chicken out', 'category': 'evscaperoom', 'key': 'give up', 'no_prefix': ' quit q abort chicken out', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}¶@@ -290,7 +290,7 @@ set in self.parse())
@@ -371,7 +371,7 @@ shout
- -
+search_index_entry= {'aliases': 'l ls', 'category': 'evscaperoom', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\n Look at the room, an object or the currently focused object\n\n Usage:\n look [obj]\n\n '}¶search_index_entry= {'aliases': 'ls l', 'category': 'evscaperoom', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\n Look at the room, an object or the currently focused object\n\n Usage:\n look [obj]\n\n '}¶@@ -400,7 +400,7 @@ set in self.parse())
@@ -490,7 +490,7 @@ looks and what actions is available.
- -
+search_index_entry= {'aliases': 'shout ; whisper', 'category': 'general', 'key': 'say', 'no_prefix': ' shout ; whisper', 'tags': '', 'text': '\n Perform an communication action.\n\n Usage:\n say <text>\n whisper\n shout\n\n '}¶search_index_entry= {'aliases': '; shout whisper', 'category': 'general', 'key': 'say', 'no_prefix': ' ; shout whisper', 'tags': '', 'text': '\n Perform an communication action.\n\n Usage:\n say <text>\n whisper\n shout\n\n '}¶@@ -519,7 +519,7 @@ set in self.parse())
@@ -581,7 +581,7 @@ set in self.parse())
- -
+search_index_entry= {'aliases': 'unfocus e ex examine', 'category': 'evscaperoom', 'key': 'focus', 'no_prefix': ' unfocus e ex examine', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}¶search_index_entry= {'aliases': 'ex unfocus examine e', 'category': 'evscaperoom', 'key': 'focus', 'no_prefix': ' ex unfocus examine e', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}¶@@ -605,7 +605,7 @@ set in self.parse())
diff --git a/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.clothing.html b/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.clothing.html index 265cf514cb..9be6838d90 100644 --- a/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.clothing.html +++ b/docs/1.0-dev/api/evennia.contrib.game_systems.clothing.clothing.html @@ -622,7 +622,7 @@ inv
- -
+search_index_entry= {'aliases': 'inv i inventory give', 'category': 'evscaperoom', 'key': 'get', 'no_prefix': ' inv i inventory give', 'tags': '', 'text': '\n Use focus / examine instead.\n\n '}¶search_index_entry= {'aliases': 'i inventory give inv', 'category': 'evscaperoom', 'key': 'get', 'no_prefix': ' i inventory give inv', 'tags': '', 'text': '\n Use focus / examine instead.\n\n '}¶@@ -653,7 +653,7 @@ inv
diff --git a/docs/1.0-dev/api/evennia.contrib.grid.extended_room.extended_room.html b/docs/1.0-dev/api/evennia.contrib.grid.extended_room.extended_room.html index 9d99bb9288..d90f992bc4 100644 --- a/docs/1.0-dev/api/evennia.contrib.grid.extended_room.extended_room.html +++ b/docs/1.0-dev/api/evennia.contrib.grid.extended_room.extended_room.html @@ -340,7 +340,7 @@ look *<account&g
- -
+search_index_entry= {'aliases': 'inv i', 'category': 'general', 'key': 'inventory', 'no_prefix': ' inv i', 'tags': '', 'text': '\n view inventory\n\n Usage:\n inventory\n inv\n\n Shows your inventory.\n '}¶search_index_entry= {'aliases': 'i inv', 'category': 'general', 'key': 'inventory', 'no_prefix': ' i inv', 'tags': '', 'text': '\n view inventory\n\n Usage:\n inventory\n inv\n\n Shows your inventory.\n '}¶@@ -360,7 +360,7 @@ look *<account&g
diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html b/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html index f934d57a9e..c485bd9dc5 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html +++ b/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html @@ -305,7 +305,7 @@ everyone but the person rolling.
- -
+search_index_entry= {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\n look\n\n Usage:\n look\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects in your vicinity.\n '}¶search_index_entry= {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\n look\n\n Usage:\n look\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects in your vicinity.\n '}¶@@ -331,7 +331,7 @@ everyone but the person rolling.
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.evadventure.commands.html b/docs/1.0-dev/api/evennia.contrib.tutorials.evadventure.commands.html index d1e473348a..86b5f2e8c6 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.evadventure.commands.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.evadventure.commands.html @@ -256,7 +256,7 @@ set in self.parse())
- -
+search_index_entry= {'aliases': 'roll @dice', 'category': 'general', 'key': 'dice', 'no_prefix': ' roll dice', 'tags': '', 'text': "\n roll dice\n\n Usage:\n dice[/switch] <nr>d<sides> [modifier] [success condition]\n\n Switch:\n hidden - tell the room the roll is being done, but don't show the result\n secret - don't inform the room about neither roll nor result\n\n Examples:\n dice 3d6 + 4\n dice 1d100 - 2 < 50\n\n This will roll the given number of dice with given sides and modifiers.\n So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,\n then add 3 to the total'.\n Accepted modifiers are +, -, * and /.\n A success condition is given as normal Python conditionals\n (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed\n only if the final result is above 8. If a success condition is given, the\n outcome (pass/fail) will be echoed along with how much it succeeded/failed\n with. The hidden/secret switches will hide all or parts of the roll from\n everyone but the person rolling.\n "}¶search_index_entry= {'aliases': '@dice roll', 'category': 'general', 'key': 'dice', 'no_prefix': ' dice roll', 'tags': '', 'text': "\n roll dice\n\n Usage:\n dice[/switch] <nr>d<sides> [modifier] [success condition]\n\n Switch:\n hidden - tell the room the roll is being done, but don't show the result\n secret - don't inform the room about neither roll nor result\n\n Examples:\n dice 3d6 + 4\n dice 1d100 - 2 < 50\n\n This will roll the given number of dice with given sides and modifiers.\n So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,\n then add 3 to the total'.\n Accepted modifiers are +, -, * and /.\n A success condition is given as normal Python conditionals\n (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed\n only if the final result is above 8. If a success condition is given, the\n outcome (pass/fail) will be echoed along with how much it succeeded/failed\n with. The hidden/secret switches will hide all or parts of the roll from\n everyone but the person rolling.\n "}¶@@ -280,7 +280,7 @@ set in self.parse())
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html b/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html index ae6fd8d003..1775516a74 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html @@ -153,7 +153,7 @@ such as when closing the lid and un-blinding a character.
- -
+search_index_entry= {'aliases': 'inv i', 'category': 'general', 'key': 'inventory', 'no_prefix': ' inv i', 'tags': '', 'text': '\n View your inventory\n\n Usage:\n inventory\n\n '}¶search_index_entry= {'aliases': 'i inv', 'category': 'general', 'key': 'inventory', 'no_prefix': ' i inv', 'tags': '', 'text': '\n View your inventory\n\n Usage:\n inventory\n\n '}¶+
aliases= ['press', 'press button', 'push']¶@@ -182,7 +182,7 @@ check if the lid is open or closed.
@@ -252,7 +252,7 @@ check if the lid is open or closed.+
search_index_entry= {'aliases': 'press press button push', 'category': 'general', 'key': 'push button', 'no_prefix': ' press press button push', 'tags': '', 'text': '\n Push the red button (lid closed)\n\n Usage:\n push button\n\n '}¶+
aliases= ['smash', 'smash lid', 'break lid']¶@@ -279,7 +279,7 @@ break.
@@ -379,7 +379,7 @@ be mutually exclusive.+
search_index_entry= {'aliases': 'smash smash lid break lid', 'category': 'general', 'key': 'smash glass', 'no_prefix': ' smash smash lid break lid', 'tags': '', 'text': '\n Smash the protective glass.\n\n Usage:\n smash glass\n\n Try to smash the glass of the button.\n\n '}¶+
aliases= ['press', 'press button', 'push']¶@@ -408,7 +408,7 @@ set in self.parse())
@@ -506,7 +506,7 @@ be mutually exclusive.+
search_index_entry= {'aliases': 'press press button push', 'category': 'general', 'key': 'push button', 'no_prefix': ' press press button push', 'tags': '', 'text': '\n Push the red button\n\n Usage:\n push button\n\n '}¶+
aliases= ['l', 'listen', 'examine', 'ex', 'get', 'feel']¶@@ -532,7 +532,7 @@ be mutually exclusive.
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html index bd6877edd3..65a5403975 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html @@ -556,7 +556,7 @@ shift green root up/down+
search_index_entry= {'aliases': 'l listen examine ex get feel', 'category': 'general', 'key': 'look', 'no_prefix': ' l listen examine ex get feel', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}¶@@ -592,7 +592,7 @@ yellow/green - horizontal roots
@@ -609,7 +609,7 @@ yellow/green - horizontal roots
- -
+search_index_entry= {'aliases': 'shiftroot push pull move', 'category': 'tutorialworld', 'key': 'shift', 'no_prefix': ' shiftroot push pull move', 'tags': '', 'text': '\n Shifts roots around.\n\n Usage:\n shift blue root left/right\n shift red root left/right\n shift yellow root up/down\n shift green root up/down\n\n '}¶search_index_entry= {'aliases': 'pull shiftroot move push', 'category': 'tutorialworld', 'key': 'shift', 'no_prefix': ' pull shiftroot move push', 'tags': '', 'text': '\n Shifts roots around.\n\n Usage:\n shift blue root left/right\n shift red root left/right\n shift yellow root up/down\n shift green root up/down\n\n '}¶
- -
+aliases= ['button', 'press button', 'push button']¶aliases= ['button', 'push button', 'press button']¶@@ -635,7 +635,7 @@ yellow/green - horizontal roots
@@ -779,7 +779,7 @@ parry - forgoes your attack but will make you harder to hit on next
- -
+search_index_entry= {'aliases': 'button press button push button', 'category': 'tutorialworld', 'key': 'press', 'no_prefix': ' button press button push button', 'tags': '', 'text': '\n Presses a button.\n '}¶search_index_entry= {'aliases': 'button push button press button', 'category': 'tutorialworld', 'key': 'press', 'no_prefix': ' button push button press button', 'tags': '', 'text': '\n Presses a button.\n '}¶
- -
+aliases= ['hit', 'stab', 'kill', 'pierce', 'thrust', 'parry', 'fight', 'chop', 'bash', 'defend', 'slash']¶aliases= ['thrust', 'parry', 'kill', 'hit', 'fight', 'slash', 'bash', 'defend', 'chop', 'pierce', 'stab']¶@@ -805,7 +805,7 @@ parry - forgoes your attack but will make you harder to hit on next
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html index 5926da7f9f..00a7b13ad1 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html @@ -248,7 +248,7 @@ code except for adding in the details.
- -
+search_index_entry= {'aliases': 'hit stab kill pierce thrust parry fight chop bash defend slash', 'category': 'tutorialworld', 'key': 'attack', 'no_prefix': ' hit stab kill pierce thrust parry fight chop bash defend slash', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}¶search_index_entry= {'aliases': 'thrust parry kill hit fight slash bash defend chop pierce stab', 'category': 'tutorialworld', 'key': 'attack', 'no_prefix': ' thrust parry kill hit fight slash bash defend chop pierce stab', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}¶@@ -263,7 +263,7 @@ code except for adding in the details.
@@ -968,7 +968,7 @@ to find something.
- -
+search_index_entry= {'aliases': 'l ls', 'category': 'tutorialworld', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\n looks at the room and on details\n\n Usage:\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects\n in your vicinity.\n\n Tutorial: This is a child of the default Look command, that also\n allows us to look at "details" in the room. These details are\n things to examine and offers some extra description without\n actually having to be actual database objects. It uses the\n return_detail() hook on TutorialRooms for this.\n '}¶search_index_entry= {'aliases': 'ls l', 'category': 'tutorialworld', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\n looks at the room and on details\n\n Usage:\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects\n in your vicinity.\n\n Tutorial: This is a child of the default Look command, that also\n allows us to look at "details" in the room. These details are\n things to examine and offers some extra description without\n actually having to be actual database objects. It uses the\n return_detail() hook on TutorialRooms for this.\n '}¶
- -
+aliases= ['search', 'feel around', 'fiddle', 'l', 'feel']¶aliases= ['search', 'fiddle', 'l', 'feel around', 'feel']¶@@ -996,7 +996,7 @@ random chance of eventually finding a light source.
diff --git a/docs/1.0-dev/api/evennia.contrib.utils.git_integration.git_integration.html b/docs/1.0-dev/api/evennia.contrib.utils.git_integration.git_integration.html index b65acb3a90..54dc39e2aa 100644 --- a/docs/1.0-dev/api/evennia.contrib.utils.git_integration.git_integration.html +++ b/docs/1.0-dev/api/evennia.contrib.utils.git_integration.git_integration.html @@ -208,7 +208,7 @@ git evennia pull - Pull the latest evennia code.
- -
+search_index_entry= {'aliases': 'search feel around fiddle l feel', 'category': 'tutorialworld', 'key': 'look', 'no_prefix': ' search feel around fiddle l feel', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}¶search_index_entry= {'aliases': 'search fiddle l feel around feel', 'category': 'tutorialworld', 'key': 'look', 'no_prefix': ' search fiddle l feel around feel', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}¶
- -
+directory= '/tmp/tmpiejj_xn9/a99fb7bcc4e6c529de9a286c187e4e11f0a90c6a/evennia'¶directory= '/tmp/tmpm73bft6s/90ed54066824f123e4c0861d1b23071397daf8ab/evennia'¶@@ -269,7 +269,7 @@ git pull - Pull the latest code from your current branch.
- -
+directory= '/tmp/tmpiejj_xn9/a99fb7bcc4e6c529de9a286c187e4e11f0a90c6a/evennia/game_template'¶directory= '/tmp/tmpm73bft6s/90ed54066824f123e4c0861d1b23071397daf8ab/evennia/game_template'¶diff --git a/docs/1.0-dev/api/evennia.utils.eveditor.html b/docs/1.0-dev/api/evennia.utils.eveditor.html index d57de05503..5be1b43d84 100644 --- a/docs/1.0-dev/api/evennia.utils.eveditor.html +++ b/docs/1.0-dev/api/evennia.utils.eveditor.html @@ -336,7 +336,7 @@ indentation.
- -
+aliases= [':s', ':fd', ':j', ':dw', ':dd', ':>', ':::', ':y', ':wq', ':A', ':h', ':w', ':!', ':i', ':x', ':=', ':q', ':r', ':I', ':DD', ':u', ':', '::', ':UU', ':echo', ':uu', ':f', ':fi', ':<', ':p', ':S', ':q!']¶aliases= [':j', ':::', ':DD', ':dw', ':A', ':y', ':echo', ':I', ':wq', ':s', ':=', ':UU', ':', ':h', ':w', ':i', ':p', ':r', ':fi', ':S', '::', ':!', ':q!', ':f', ':fd', ':u', ':uu', ':q', ':dd', ':<', ':x', ':>']¶@@ -364,7 +364,7 @@ efficient presentation.
diff --git a/docs/1.0-dev/api/evennia.utils.evmenu.html b/docs/1.0-dev/api/evennia.utils.evmenu.html index 38c904ae98..c31d17a435 100644 --- a/docs/1.0-dev/api/evennia.utils.evmenu.html +++ b/docs/1.0-dev/api/evennia.utils.evmenu.html @@ -931,7 +931,7 @@ single question.
- -
+search_index_entry= {'aliases': ':s :fd :j :dw :dd :> ::: :y :wq :A :h :w :! :i :x := :q :r :I :DD :u : :: :UU :echo :uu :f :fi :< :p :S :q!', 'category': 'general', 'key': ':editor_command_group', 'no_prefix': ' :s :fd :j :dw :dd :> ::: :y :wq :A :h :w :! :i :x := :q :r :I :DD :u : :: :UU :echo :uu :f :fi :< :p :S :q!', 'tags': '', 'text': '\n Commands for the editor\n '}¶search_index_entry= {'aliases': ':j ::: :DD :dw :A :y :echo :I :wq :s := :UU : :h :w :i :p :r :fi :S :: :! :q! :f :fd :u :uu :q :dd :< :x :>', 'category': 'general', 'key': ':editor_command_group', 'no_prefix': ' :j ::: :DD :dw :A :y :echo :I :wq :s := :UU : :h :w :i :p :r :fi :S :: :! :q! :f :fd :u :uu :q :dd :< :x :>', 'tags': '', 'text': '\n Commands for the editor\n '}¶+
aliases= ['__nomatch_command', 'n', 'no', 'a', 'abort', 'yes', 'y']¶@@ -957,7 +957,7 @@ single question.
diff --git a/docs/1.0-dev/api/evennia.utils.evmore.html b/docs/1.0-dev/api/evennia.utils.evmore.html index ef84f40185..e06ea7882a 100644 --- a/docs/1.0-dev/api/evennia.utils.evmore.html +++ b/docs/1.0-dev/api/evennia.utils.evmore.html @@ -137,7 +137,7 @@ the caller.msg() construct every time the page is updated.+
search_index_entry= {'aliases': '__nomatch_command n no a abort yes y', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' __nomatch_command n no a abort yes y', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}¶
- -
+aliases= ['top', 'p', 'previous', 'next', 'e', 'quit', 'n', 'abort', 'a', 'q', 'end', 't']¶aliases= ['p', 'n', 'previous', 'end', 'q', 'a', 'next', 't', 'abort', 'e', 'quit', 'top']¶@@ -163,7 +163,7 @@ the caller.msg() construct every time the page is updated.
diff --git a/docs/1.0-dev/index.html b/docs/1.0-dev/index.html index cbc0f58e69..3e22c681b5 100644 --- a/docs/1.0-dev/index.html +++ b/docs/1.0-dev/index.html @@ -414,11 +414,8 @@ or the original github wiki. You have been warned.
- -
+search_index_entry= {'aliases': 'top p previous next e quit n abort a q end t', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' top p previous next e quit n abort a q end t', 'tags': '', 'text': '\n Manipulate the text paging. Catch no-input with aliases.\n '}¶search_index_entry= {'aliases': 'p n previous end q a next t abort e quit top', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' p n previous end q a next t abort e quit top', 'tags': '', 'text': '\n Manipulate the text paging. Catch no-input with aliases.\n '}¶- Unit Testing
- Profiling
diff --git a/docs/1.0-dev/objects.inv b/docs/1.0-dev/objects.inv index 763fe902b334695c593eb2371aa62641f08b463a..f25fad53805e95803391cc94a0ee87a05f312437 100644 GIT binary patch delta 54348 zcmV(
3$C1b!>r z2azwBY2qX>Hc31SDWFmBT9@3&YT32cDD1?cA;>W2Q0@LE_fW-`e=0*DI7k&- Tk}((_XzX&&ZnwJmO0SfD={~Owm*i*9CXsK9w)foznm}`+G`x zUcO*21)*Z?DP#k!FWOJ#3k()yI4V6XoX|HCN0_xrfA6bupH@u vC3%5Oak=4&5Rt9~C=74c!nU{_{WR)@F~jEf1pPM|9hK+!Au>RtEo zwOLEJ@FR{u;-COO!j*L4yd{6^XN133GVZ{@avv<_yGq{5*)XoTpPF`AfjfD@N?|j6 zq*92Xe^(0q%u4N{-K-lpd+^K(2&-hBi?>!*kelx3a~<_PEqW;xe>0_Q83EL`W8+nt zs+4`D&yei9wt)j7v9RdltBnQXR(eI( #FkUULPu8-godV
*z}6kqLi-&luGh!~?xltfa7q6RXoDj%X7l!^GJy)&wKcRpF+hWEq*HiEe79J~u# zp6#-RZpG1{mbFfar86|EV?uh{x#O_-oZNksD@t5MX$yW@Dt=6xmo(*vtsb@4{gdSX zEhSXZx04!#?)Ou4w9(o+59MKc3qP84jT_|dqM*}Yh;hb0aWB1 =WM!z(oQ=~nc_E$gFwuGU;CNF;ER0i8ju9toxcvx8sJjrl zW6;eY%mYPzXO{+fslAJrp8)|GfA3`BYhc(G9-qG0Ijpe4_P;BBd>fb-L`?d!ZT9@s z&|~_PJZF}kW}t>A+IbgFXc$pA9IS|NCZ+q^$bm^^5bp0myNX4X!X>d)qegU6P4fV{ z?Sja}@~eUT_KZ>%t-F(b%?!f)3Qkv?M>~?L24E^hYzhBd($(z1{IWv)f0aD3)NoII zk*8y&AEd1*O+TOn=^ca>hNAQ#@uFLu#W<9r1Dd5RZbKHW^ci!bVVS(IXH!5Nn$=m- zvM~3=d?SlSTG9{}5};Q_Y{=`Z-sj1|W5P5h8OvUfOVsKnK0|6eX9bt!0w_C~H&8F& zmyN>N3yuhc#9K|zHvs2Xe_xV%m1S|GHJ4x^(n(*_B^h*!8euiTtUy|{L6jICj2ByB zX`k41hTnoe5G0fK;_8Zxu};BAZCFNiGI!#ajRGFzm{iCIg7@sxuXsb*oW|G}yob{M z!HcncV4V}T+3=?Bp$Ve}vvfi_CTT+a>T7K31?E>@+myhDZN>Wae*kJSB8Wox^A-H` zpjoGD+YuWR5f>vW`tTxymMxr1w1+^&etR%&vhlfVClLw}5B&6?$%?16%9{ECFHMH> z32D5N$%%W29vVPNe}I{P5}t`zOSmktD`-P*T8q9*8}m4%3nIoLw5 YW;RsU)u2JSd}8scXEoa@*$iEFR41rbXHU zGMF5?qJOBHbmywx7-&$z(aj9mD`6dpHF_P*P}IrlXrcP8f0HgHc!kH&C0r $GC6 zHB9pzddoVmlRZOMynqa@5xSb`L)x1 r!i9vFiKTI|)W&f?PN__yT!qXE_?SNxlSQxnVozrj%DHM5?%FBw7`WI8r5f z8H0yVZWZfQe>@b)E?WgB%24cr9NbAl;gUQ*#5A*CPvq7^Ty&lh_f#*2X?03J)8*(y zz<4S$mb)aEC=r}C$~u6iMrdOU;O>ryWt^;<78B>CtUh^iSODO@k4#?S6@iyY8@QTN zZ6B(1JAUxUz)vDEiM%f&DR1P!n}Dp{+O#>JVIH^ze>2~7wL;LfLh|I3 VxE2yrq&0#!=tRr?_TsCIJ6KiEwnd85UbG`Q=nmop&6>EDs(PwTL|2A9LJqc` z(1hin8;TM%q7sjnz`97%m?Jb@hf;iG ~ zZaFQCg?SK88V={q5jk72)zTCv&q ;HFVy$82sGOjzLV*;TD=z#2=h=CY`z8;jfcFP|^hSz|m>Exd&ZVEWPl za6_;im`>pyTo>Gl%dLx-8^_V-0sc_zlA9tgH&Hu0^{n#cNn7C716eN$iIZ0Jt#HKB zf1X<73U(5>Tu@}ka&N&D1J}SBUFD8yP+Z4y%7s>YEV)%EQWwndth2^`CZtbomcK`9 zQoK;JK^FLoPKjJT!;v&&nFmi%;^FsLluG-itacWKC5#BFL%SBQl 373Po`To&@e~`-SwH(xE^jr+;Sk!Ycu+_ S^-Q2u>0Wu%r-m~Vg}uofYh=QZTHT5O*jGUSEQxl_YPj9W zW^wRrUpLnc(MyjYwYr0Apy(ZS-VU9pQ6mdoqVk97q($g-q6SiWM>}(s5{UdpKi=sU zmjS7w2PtNT>9pfk0`Nk(xKfB=f9xtA(z+-i4|e;ovVH(7DpFGF@wc@8=3N<)vz0n1 z4P;F1(b2qNs6~ZG3 5DIL}bD~&e!Tl9;_k8E$-OQU>4EhL&FA^G&$ zdt1bnafNtUDu7KDyYGe}+{w&XVh0ZGuJ15tEOjqjBJN_M``makd =CjlJzrAbO*`&>H}WS(*=NXE;DzBG2%7*N;2h@dt! zI<_8w*%P4C?K=E>T>LY|6n#Zh@k1j`k!>W9Ub7BL2G&5r(>^yze^sZZRj@7qsRem> zMW!z0vq0pphwUxuu~Dr3Q1O6GBPka2q>Gg{Qw_XNqBkJOsVX-s?x7Crl9N5!w1UTp zlDxaH<*kne;*xts+$)k^7LCZFGM&(5K6~_GLS^+pTXGNKX(sGA^D~?hHb&f-ZxMF5 zJp3zu&{Kwgb#{LLfByF2?*8oE`Q^j;+1s~gKfb-Z3yn?pYtFbRKRsgd`op{Po3q=y z%iHPkkcY$PjD`gDiN)RJ+3opHSMT0 Cl}Rl%Go!3=L}wLcO9D? z@|Y3Cakw+#5!Fs@lvQ0EK9=PtD8=x5nsT0i>XI?!0)&G~0>AxKhZUK}q~@?GygOiN z$3}|k#V=IXe~7zUlz hPYWNKPMH?rvFK~z;t|nFg+Uea zs~y5#@3bgtrG<%K&xq2qV`wsRni3{}J939Y?yE}s*p&Nh6+$jW?!+$(L6feYz+PJJ z*m@e#3 Av+a%9R^=t~cwG=wdpcu|e=_Hu6kx=AqFl&zS3jYgKEg z1&PUZtv-sGO$;i0X+(Jb*9kl&K{cXc^68U+tag>&To@SsGvmQJ)Wl4IMXf=`jM41O z6VgvEf2EOFAH~jw%dbI1Bq3$Z@Dn1Ui1^gyyxeAmH#mjH|ILuQvap^_8G?%N{I409 zwnV*gd2sq;Mzt>E4l%V$xqZC02u*-aLjJPB2PT6Q5I J{D&ZJUxG zn(l+MlX=>tv2uz_ypn=8QSsG_f?Gv5s@<`Le`B6SLO^T~z!-=NL}b0SMElw#ppA)q zyn*i~8y?u!DQprw7w tRtZ&ZN!4*=K;`Q;b;AE_h*VN+w#MH`-8yo#V)F}7e z%#cc-{G^cR;-R4#iiWUu)0rdqjUe4jf5@U!y)~t1&0lY3KL!$*V72+sq7cPEMscU{ zG!>?Mm4`uUtfg_-{%vh99mgX5d)<2dVN=`-UIc_a^8wVm%sjS0KT3c6Ig+D!<}+-+ z&>2*tDjn27;EaFaih-7Z+k(QUd5g@}#w#RaA#OzqAMP!TvGFFLfTLu2uRKaKe@;;D zOG)~E{L+l@@22nkjaSk0CAn>RQH8ex@Yy^0MLj=*I-Mm*KXxU2XCTsmZNIVFgEd@O zRlvksb9h0wTe{#@m+T!xkIDx~JXl9|#N=WKAMlPk5k*bc<3&AHL|}V~QLj_7j#Unp zphXFy^8r%tV6vXN8>q4JWLL8)f6JYxA{6u%{x^Wt?W1@Wo=rX+lb0U0R`O0d!qnvb zXd3b!4MEyg>XZ)=0* ^O=BTe^O-R{Ku9g z^3A?UnWB}JPyP87i%G<)kRQ!2ZJJwcSdTcQ#9#Oi3wmAdQulmsM9~+!V_(`?-H@zh z(Fa>GAxxdjo&0IxfMK~Z@q5bB3Jz%;{27f1umPjY8Kp;0o3d(>JU&B{M%A-%`ZNm1 z8zgj&&d{t8I9su?6p|y^e@K#q+K6f?H2`)cs 0E|RfFJ;#(khhCvhWw+2KjoPw63yLlKkS5q$cv@iXh;e|%i9V uYHhpnK? kv(Kk zipR)hi>jxAR+3Cm@#u3$12dOQnkE6$p0Fg66a?y+Zuiu1f5UKXL1 z!c$eltKeur*s=Yp(`|sj@AeA4$QK?KN8fM8yR;*n>Ja+cQBCBgCEj)78R|)(6}=O? zo%|9Ox*Evw8dF|ACiw%`7EU`iNoMOXdOCCn@fZO*t~YPr|9JNH;qLtQ>gN6~lAb1w zSaz(#8mhhse;5p3YtuZnu9paJ-g2koxvC7IIagbgpR?+uz2ylz$nXd)7uMApMKgSM zT_9OscI+!Nmb+G5MvpnFl%rXdh)M0qZ2{o(vqa9%oLSS~atYN$FWld>acMx8M&-?O zeuG)0q@N;oj9cML_tyD6XzpuLi+l48Jl_- zMjH2Cun(e5X&wRWu;LoSm~~YsoFHeyKT0C8Sqgn#ME)k#q+zgHiLG0ik=U8E8dh7= zB8Wx=)+FzJvNh!lc=4Nn$#x{vZH86pu6)KuFtMEYk!WHEtS?4eU*FtvM>D0LH6De{ z*A|ape^A?y4JwB%8ylY{HQ##iSx)UV2n19cs|}_@^c7wkq3|?m?!teqi$izY3k)Ce z%^@0oC-F98b3f3-Lw8Rg`HBhJR1%v}IG9f&3>L9qI!!&x3 u` oYv (%a&0zyFZ688|g;^vqVT#o1z^XD=loO2J>(7>XG^ss2N&NiF0O5t*{Fe}t_ ze<5NYYwaWb0x&ORP-(G92^twpxDVoh#~@S!FB9M+we2u?jzU$ C0r^x)qaJprTEf9Rye zQBDDh@i5FEo2JShTicwZ&t%8oCjrGsnvGM=q^D7<$2QBm $%#ZppZ{NK8rvE*HmDI72_a1pX*49NLORLbahNgIuHFV2)b&zf3l~u=(#!+ z=)dS%Irsb7J83<@VumI<$F3Ma7a4tp7uChi{m=cSpT5$xZ7Hst?elia21(*?fWE@Q zupS6?r69see{@4$(N{doH~-zL>9)+*X^|&~vh~j3p3EEkc#9uK7SaVxA3Y{;1JW^Y zQQ5Sz6w63DYKAZ)c09-le}`d2j=zXN58F2L&lD$I%-|qvxRTctP$@z^Vr%w<*FHtU zqZNCp=s_)cj-QtrZ#I+sNAl $2nQwWUW zXR=}CZI-sl>eQfDd0vDQZ9#4F^2JvXGXOpD( %W-n$(hI z%$G6@Us-=47Yzg-$P0^qu&lIq^z;&2E64`z;`%TwMeWC90EM<&%YqscM63}N&+8&F z^lBDzKD1^n0c$ECoaZ&Bo*FI?K0-%-RK|Rr$*LM= M@LQAj^sGadrkH zdUUq)vsA%+Ta|fP(LAAU4%Q0=I#nz%Tq_ p*i_#)GwdhHBG?2S;oox M^dzF_?kIHH@uP;t8~xg&gw(4if^hC zXX3qXOc#Q>GIWZ#lL5=UJ#NHz?vk7Nz+taM#* )~J^)t5N&3N_39| zm^JXgQYVXQe~?H9vFM7Y5&hMn;P}4 uWN&lO2cexT0mSCgfIN3;`_}Et7S2UhQhUJh9 ah#e^7{dh}2UhKqXRsWEdO^$|5*|u<8(ROhgP|k$w-~zovCAk}Sj7D0J zX-On*YSU8NF-n<{#3gWAwF^ovJYEf#uHm>>e-*6LKpg4{e!}rErKhL)V|*=kLl{nc zo37Gqw20^-Ag(GDm>J>#QPXXltkXUBWTR1df_Y!EkqAy#D~{o(8WwToO$I~qIF;># zoWVzQC$8Xn>aot4?lY(|#JS+#!3rrCkBiCL^gzUA<^K!5@H(ZZDl7)&mbcfma8Z?c zf4XPwxOph&*3FK33%=Z_94&P@uTU)`E2RxE2> zx5FuG9ptC1b(C&uRi6^4BWY)AEAnjh39lW%=DWU&9faYpvJ1xucJ*r`A~!3|opMQ{ zNiz-2NYqSA3wT=n(MvogpVLj!z8|!Te?^_)LSb7OHSB=;!Oz~^T}Avuhs}@2!f4zB zx(hAN)50+QfBttZau;m}C &!LyYK$^r-1YwiTnq5 zz$gsTnSR7@J`@VPp%e^4i2}h?7k(B@>NqWHTY7vUbGGWQXle0Rkftl{6Gqb)e+`LL z%2JwI8@iWPSz0wcw-(`2IPx2hRlnKMX|hd=w1W4oCZ)hg`VBcmE5CGrgDGMi(pCku zB&Af8Qdwj`gVckA%arfckfF*EEQEAbKl4abMrgDu*&~ zHb8TD$I3ermLnTbgHG~TxW}hHxNndWW|Z;a5$@Be^`?cB%x_D1SY=qb)ByZA0=Puv zq6fnE5Wn+McM8S?-51jH(Boq9y|{k(x@Yx!sJ$_?c<3Z1WBzb051;H>e@Bmp@#>lx zhD>;13yL=)2_$|zI9C4bin%cT^=9LaBH_ms{@(2^jnioyP$bdV$4(%3!qHw1oxI7} zXRA3I;-WVLopX gXfz%N4GwiDU(9<(Sl6N zJE?qUe<@fU9maj kOUrTD?sulV=Z zE;iQcPoa}yf=@V@3FRv _OJz8hqS2fQlclRX;S;zno|bXRYUP~3EfFdPz? z;|6JN^g|dtahCwXk@iyp3xYwKw2(@;i$xDdn1+~?A_k0}M&h1$e_-ke%%c)%1fti> zon+L9Oe{TmVVX4hmz_RJXtEfj2TBAc#Kf{=#pr3|Uv{*ZM _`l1`?~F zaY(Cal!wh5k!;)}vbLvaGmz^$Srt71=ps|fpv=&6lYT@#4#xZQp_!-J2CX$QWtwLg zD0KifE#9Rs@ilWYf2=}u-uy|6^d+Ki9mMGLw6vp358vx!GT^Wx^o~*szX<(I3Jr`C zK>TxgciB@VJI27u0s6{&&nC5`2}qpb?z${+YoisF)+2FVytC$!jDs(uBH%6oC0`H2 zTiCtq^QE&P$Q5O{%H1mfMuqxza>CQwEZ;Y+-F5Q*Yfyxt`@@np* z7 a48ZVUTMKf*=tE>9l 6X-~D(6l+!k8UZjnSg@?MwKL(6A z-tJa=KO_oDaMAx0y}3TWF{1A3wJ$0Lm8jQu7h_cOMZ6*A-~yh!E5^g!m769$7ltK= zl~MZg!zt_&?G$U|d&^Z|#M6}LeaxYZT#o|FpS6KUf4w_VGY{R H#wvIi{uN;ean`c@S`|DVQbk8B(Z4V?`{JWnOOqSQw# zT-TnWf5|}5ln&UJ$JZbJBTi~I2!&%*>!ez1)7KD|5}F1D%ri=AHeMB;PNr_~vTHw+ zSFI{Bj;&1%Ba~A;u;}nw-IyK5UJcOMYj{1{gzbgXn>NthqB5}<<%d=cy311rMLbS@ zjM+WTehhh3V_x*qDV}Vqy*Z5ls!=d{CKV4oe_0YA@179^8voE `EYUO*(xpSG>}e6y`{e{k`mr2 zuA9(=lw$AUZR5IeCSc9xl $FL- zJT%&4`Fr6KaTk{gZE-|d8VG;reY%)o!z9ypICfS=@UQZxhOJ#^&G^!(K9D$(e*#Nf z^de~_yv)dQr<@kW;zVV%#7E`)XLZ@FLztvVm-eDr^UBnLIvq;fhfy1gZ_ZV(@5h?` zm{iT^#*Z$Dxc5uJ4ZRN!ikE=lmtV)H5DetJ>9Tdo=0-4@DfX+FK-@X35}OdNz#WG^ z?{3Z`>26iVzozyy@Ne-uuC8gxe~L{Rz%~%<8YK9%F=~wnT8R2~1V;gz)e}ExT)K*( z8dMZV<`#AG$|F~HX)K}4C6-RccemVXn4yl*&o7 ML?05)N?O zI@2CpcJXkrcjBzFgG9W-Mp}il4dk7XY&RQ6%0UQmhnM{{VBi#Czt0bFe^Z}1xHOlj zeW^3}h!Ui-Frgu|+Mk1mM=NQz7=RA(3LaF8ll3!96W0MG8e#B`&cQAE>Wn*n?NXp_ zs y5au*O4ebj)Xu^~Q!}(llxeKkn&F3nG@rW`-!{s! zfI d$7mCr%IhGA9f}@f8QKJf&%CC*9I-lC%{I1V9sb{<$(s+?04oFU*Pl_8z{6r zv2*pyLdKGzN&+Is!(ZYb3ocz%!en5DsGd;Qj0kU}2b9O!uXb(O94{~*JCQeRHt5Q= zr! fqO^p 8~4_;dz5+>u-I4t85GAfCH1KFLqB~?gXN6;XIPUhTa`K zGQ9j`rWuQNN0hqvf1OOku!R(q??T|O^j8PXEsk8LMe7* sI!RsMBGQbmF%S;fAbb1 9m7c#u;HHRH5p$e0oX47 xukQ_FVq-;Z*kXduceK1X{4v;{lIq=J=Gz^f|2# z4W3M0B}M$m=A6>fbGVjPv`gt3G0*5RW%G04>7nVS&GWc!AE{MH>bOq#2^+hlrpMge z>bfCcvk;L8e*p(dL xH<#j~rKQ$lf|^5sH@t!7-vKIMJSVq;S`?uSCXjzi$d`4) zl@Nk_DcFOmNNYHUiduh44-+CjLU@M`6lZYo&;m}rZL+FvCN{zYc{q58pT5noD8#gy z&-!%Mdw!nU8ri_^gdc7Xa6eOfB;Nswalyc`*gXpUe@d8uC+CPed+>B-$?0h>+VV`A zhrBF5ITA##h6XN9;L<+BJLg8g;hit?dc~>yRppK=C7q_IIDk1K?JC4bJqMu{jRrWK zlX>WA(5WnIe#HhXu})N|4|2}GS!V_2T|HIt8~Ykm5W@Tz^Y@;dutinv_^orjadSU@ z9nh8Pf7Y2i_fpGy-_NLz_ZFa51HK@W@(y&G5LA(Rs7beFl}*i| zG0gitjf|y>r$V?uqr6i%O`j3U9e0S{cb5A+w1J6z`s~p`8uU=);q2z+^R>2f)N=dG z$_%vD&BgP)q(QH>XXAl{89w?lgOObY1=ymqe+u0Rd2$3C#TXFvBzp%g?35wxMUOG0 znNaw=^1Vl7&u%bx7hfmUr*?1O5$%QKrzzKB7l>^7MLY6!(1=U3yv0VVfbmXI!4k8P z5@w_$wDar_$I1-)LJz8t@-yZODMa$E5JxtPV8S+`__je(9jH;?bi}1w+`1aGnCU ze=eQaIMMbk1AUI3C{D*MMM34{7j*n4nnTusFu&20ndY2*=6zL$rX-5ou>V}x(8G1v z3uoLQP#~KD1iH*~wdkO;PNOnkmoJ5@-1AVP*z->d9u-3s+!iMlUJ>&p@-2=%8jt2k zp$KY fA6d@z}9}1uCl#*%&Kzf#JL3qFm%A3>>8~2 zZ@+VsefptnP}J9H3Om)ka|UqHbV@q7urAO)>Yr&SH-0Gu$Nyuzz!h?oqOYI{11=Io z;NI8Gp^)FP!GQ)s=+bx~Q02&q?yN7|;PD#B^$7I{SDh G6lvw&hW(k7rX4_JLpV2|jO^Vhme zeNNV_qlC+__Dt=<@l1U_6Ao5X>gM|-7VQT#J<$+K<2_>spOjOt8C#h`e~QA8Eu<(s z*Q c42J(Ve zD0L?&FNJBy@O=!{JKj2*7d0erV7OCcsA@e4Y-D_H-HwT*;Dzau?T9KB4nC!OOxPC> zjVB(!kBv6reArcaJjfrZHsBomzFpv$*{2g6vavxGYy9!L6>jlKe`{LFc7g_0w5y(H zBs|f2zL%z58_fnq9(RYe3Mw~t1xoL(E_eLiLnA!mF5{?1yw1yJLh`u)?^XXuO-%G3 ztNc1k^Ytd<>(QphL5AWDLsB0i_(c1qun%tm#u~!}Vj+MJqdTJBC)CLY>#%_jmZLk? zW;dzN!K;B1I)K+#f0u7BqWjD1o4058m(l-w`M1089eU7KxwE|=kV~oe{D+ICN`Gmy z%6Uy>KqIAY`41P3&pdR%Vco5K10}wy+ed48l%e=TC*sqB$4?z9;AVyU@QRo(QNXZs zz!fm)>@oV}!+^@34_On#s}b8u_ZEpYtbDNr2Sm&>QaqhZe}Oh&blf3^U^MI*X&!I0 z2H$AkdL1^ xiwz{iig^ zn Eq4fZc=kaRZ@zaA6U66k
HGQrMEXUW$KEB9mnDtoG zX&hV_f`D5hf7kRPxl;rmS6gLSykm~A9m0)D#oU%JD?`mD{?}L@BCTbpINg;$XRgM> zpytu6XLsjUR}qtszJmQ4X-kU(L?7;7$Nw620Fek45y02J$wN1AcAAIM{UX}Q2j`6E zB*er#nu&bxOB~StnaD$y{q;(~?y=u7W0MXl0$fQqf1WJXE8w|!Vq%x#q5(F?{N~U+ zIR*f|kYaI?$~^A@BY|RAh3_H6_ia;FS&}oIlf2xvsXzNzR%}$!7up Dc4Tf<}rSvblX?UyT7jeVKU_^k_Z5CknSC)`m6n;6%;Ea0y0iq((DWc6vDXbnNn zB65j@f6F9g*CK<3mC2@_LtP#0TxAJgI)FeT;&^$bhC6F4B%5Epl+}8cSa>qQc`FDe za{_P7@L-V$HZ88gag+QqON=BQs}sLOB2@JH)!D1Ryz*A@GEbkKw|pG3{G@F3% Gt_Cf2F+_316lq<0>A?*(Yp*0F@5*AdYK* z2641A$k%w#8)`UgbObj|YMkVHUHUm|#Jd@@ UzgTK?J?=GlI`aIv(E% z_BA}J6QrKyC8_Y)tt#Cn)tcV#UA1U3EkHrhdsxuUqK{{{@2=jxiEc07oZVi)ue;0h z54TtMfAi6_JGG0w3*6JMC#a}}QO^H%f36dhnn9tlG69O t{Wy9$#yWF zoX`;RXLZ1!!!GqWwrfy94|OhlXo}eRcq+)B{=zsxqwaC)(V(9e>6|>^;V??Uf2=SZ zfgEf1lw?J^o?Hh9^5^vFU;E@Jj9#D 11dk|TF2GA}DvTZ_B!5=w0!o}< zU{l;(hR@8_)74Z#gldYLk2-u4LhLB+X<+DeY+9$774DeDTXETjUUTibBe6#^rGa+f zT?%iRzuk_CR|8H5vczb5l7f8zI?^S-Jyh7H6UG}Lv`u|R7~z{HDo? I%@-kBWV30qGFmWY>Ir3ttU!e;NIMgF zZ8Iric~0nPT}`;Nl| Ri6>g4R^rGaBbH}u?xUv zxv94fJ>vi<$?XF~fA5Rbm#}#$ClZ3BAiRGmJqhb+Y&P2meV(8$qt6vEzvy#91y{Qf ze@a!-vs3XsSUIG!U%m!|a2|KByI{N{gm0hw&5B@PCiNMEGO5S8ld6W?^&ZljP$njX z&yDM$3f2d&uCuCgoFkzi{Bd~RjFFoOE;P$Jf}re@Tw=vyf7eCEk8L36j2+mjgxg83 z;a?Z7f2>gPu%fF=XS)HZC1wL!EyNU6Lh^E`Lr8|UpTWcy8&RC^*0m#r)u5EfZ_{nE zIz;4bzLz)<4UOK2cX|x*JzZ29)vbN+X6lsC<9H?s803xl<4BIipdXYH{+6M#0p2^j z8X~-0EXw|`f4r{^hJsy@*C=4X;hVqm#y7fpsrAg}hInPq^SH8Br!mNxaFWNDeY64N zfMt=mS9Ek*D8k)OFIKX@vti-k5+kHtTsH?6ovO_zWs(b#n=yJ#{#2UKKSF;WrPDmA z;8N-OP-R7$u+cSZ#@SRVgP2cH*^YNfzR~)(U;ptpe+=-u=pI6_X&Q#`QCWGbOih_7 z32cN?o;_1jw+_^g&FS(vlv8jN&2E{izghuJwoaRXCVR}C(;JQ9tXz=(_%LC=4ciCn zqYH--3OnrhBl8F^97Xc!QybS=4v%r~vPp?D#`x{m|Md $Y=ViQ^9(-a7^=DzNGn-%E<-k&zR|^{rlfVpKBVI@h~(X z69RXJIi(2fZTv( fIBdJvS;QA%%dK2(8h_0(D}gQfO7q 8)SF`U8n83jA@t=bt4Qq z_qEOHMtA7f1PN7Y2UoP+mV<0sDIizkSCA5WHCElYiJx|(SRJFE7yj%?i$%lJ=)P6` zJ`k2Qv;FhibRGZLHjU#7a~F(v2Kbabe~P7UXfn97jvw*MH;#Qu9YDAizUJhH p=%3L49bSwC=~spHdO@kD^bLIwZ0;s&{$$5?{hi zKWnClP&`lAX9S-XywDu!F@$A^O6jvTGvKnD!tJ5g@R5qt@@DA7EGc)N Hug8{|f)uaMRCw)=5ID;@t(gX5VzF<45QKx)k1Y z2SV-wxRZ68w9ckZRfNL7f&Xo=e<8(QK@TbMl8enXR_?1{DvgkM#eaPDwGAI%?)3-9 z;IH6+8z_rfldUNK0>FD>clZ(Ehm9+ggu>r@!Qw5aIux8sp*voHs+~mp3M?5kSvZ*@ ziGDZo+esSCReMx93P=?he?PIhn+G**MhM>QVk>~Ze 3Yc*MG;6EkF=?9IZXu}) zCJvueMMP(2gfw58fctIwTNMfh9u%nxej0}y f1n}lM9=vTA3kwHMVbV7kr57KHvmmfiKmQ3!Dmdb(*}iO zlKu&Pe`6ZtkBLLZ2^#8<@xq9^pL%$_*W;M0P7M=MKO@j|x&A=#ha&r|-q|pfe =VAh$Hfo2@wh4(mhMB&kVGG_wnAHRJcrIigeqWy@lK47e|Yk 9bNaB(v^`_2FOSm*4KqkZLUNQHKBHZ~ytG%Ipmh4@32WeQeo> z5ebi(J(g{QHvslYfAh4-3iokvl|wIo299`(;6^%HwRp_S)h9TeoE6SnXg&JA;E(hb z;g^+goo koOBU8DdNuucaJut9SC1=7r4{%AAi$c z*XWV^v;zZ5#}>N+FYZ_P)Vm%@Ur=1lX!VtFOju9gvR%vQe=)!d65@UgZd9@vYg776 ztOv9 |nR z3GWEJzX9)PZ&@x-RrK$D`WpFnJq9-Cse(S=`nwRCCatP=--NfAL(!}Lw |owNGB^jEaAy*D11>*Ocgve&;xuVS6aBncllfs ziqD6?hf)g>pZN+88``&i8*v90IIa;vR G!||VirWz}7YZ2=)-PIa63!9Fe;#-zC4RVAUh29jE9VSr#M8-jKOJ0; zN##6GG34Pg^v8)`rw_EoREM(2!@6~(!Z;b5)e{?`tkSwJgJaf{?SHy>%k=?lXnBkX zN3=cB-j|EC-E2b1Pz3*Xf|I$L2ioBbBjE;cP_zf+Ae9fHJfO*y&>WQFu@D~tx)jLE zf95H;j!P-=!^KkOW%aO2s!#1cFauFE7NR{MC+Tj_raI2e&M~8u>wY@9{Cf2v6PNOA zkl9E%hhc0mjS!Q+Ttro}32*tOl=$Id!3~}VFw_GU@&d`|wy_i*fH|nw*(#6^CEz~? zatgN19&%|o#hU;H$%aUzfVk+MlQkI5e*>H1VRVY1^4m%0`F8u5t`oGq%s!ywFAYCT zAnZM*-2-xvZhB7-dlPnI@|TOK>ua1(1}Z1r^4@2(@DxBU;xIk}8ag(eG*75uFs< z8lScu5M|AS_p7KQy1_fB127l0b51_u0}1VXypK$eY(6+&=N#fIk8^eq4ldY9fBYOq zLn2r3zY`qG%r4nKtd(~<1DLqo$bBs2$B=$H?7MdZL+QC_56D5f6IM9AxljWBb0Y7( zOpJ~3rCj&Z#l=RlQ~ESKMM!cTgp13=#?y$0WOjP9P6K>jb4w! ^t~8|k07F2$ zzx;qkdmdK1bsft1Ss45Q**=tm7fc&Gcz>Em81fFxBJ`mgynE#?yEmZ_;(r|!boK9n z_H3rckzX<-5GAq(aF kwRd!czm)!oDr01l#tt`R*H{C<@;6&HldMv(&IqC>0dVrJ^Q5`Q^h z)?>hi%y~EeJvXkA_Vn>MMnB>w8N=!MA%4n+o83|R0FpH2hu9+p#6{OXyzbA|$#p-S zTy$FY!Q)u;n6Gydx)0^aTTi2`EpJHliQ2&_UvDZ^Q2FhmYqHQdkqG|p0DtKpq4j1^ z 1`mqC+# zX4z*R5zEkLH&^|06n$7^{G~PCIT!+~;eYIY8^GlvG>lEbt`;6IE?1shW`A*M$X)e{ z*2a94P-T%HxCQJCvD)r{bC6CpAgLV$; ICV}Dc1t$6SJu>yoQo8mFA`oQw+vzG<~5%KgFeHvJv*+>IU zwX#HqyMNEAAJbS&I7QOQ!1%dgT_p?a-x3c*F8QaeO@2y~wb26X5MKuViT}`J$`iL< zpj~Kr6xRS-1SiNq vz&yXXm4BY_{t`U0fu4GG*jR~81G8P+u$IY7fxg6F`BwyAMhYCM;iIf6 zy+624_pBf$=Cc_ZRy)XF`Jj&nz)^u3K(8BrcH2t@5%wefH2_xBZ()B8-6gKl(TTW( z%}BkwU@xGL6o^IyT_8G>#RK*9 h za2|7(FEE&sgF7-H$+Flh*BsK0`8gjX#xPJ gzfL z>4mfa>fbsTp?}F>=i>E(5spZ!Q5I^z)UfC3*9aDlN3o7ee}-+0JPeL6V6iSv9$g#Y zuM^f?!J)ntEK2O)3KummxJZ$#pA*)Hz?(7U_)OG@JrL}I2ah0lM8Tu4pxMG@5D`A& z;lCd0zRk-=Hf99-CV2{W|G23Ymzrj|H|P^dpGf!&sef_0%Cd2jZ5?E4$m;~3jfCGr zz&cH;)suHVkAmN!gE^n;CpKhXwT%;4hPxBLhET>w*+*b%$b022^#LF(YG&i>f+M&t z+z0VXLa`}BRj%8W>>CDci|XGuPNMo~;jkJeMVzt04jWBpO ziGlg+ iin$Z4?uss78TKcIoRl6bRnY3dApVKwnRB8Q0Z-S0ip^wmY0DBgp12$W*`t!qd zg?H^bUgg=ysN)gJDv)Q<^@odyKBnsktA7yuDR*om*Ygzu4fGkVdf?(TYht05y||pJ z`=jt})CZW4SA2{+TW!O86}4>tW>R>FdXL|-=`<^QlT^*O8hCnyEk0Lgd#)DC6N}GT z>6JG+-yg|0?v4iUo~Pqi3G@@zSt4O#S}< FzN<_?`cbDt%8yW|rwR(Jq3ZQ?zw!`6*|FA)?+E8jJG zMqp-{JfqHYme^`npXP~}fEG(t!Kh*nI{r(Wtv=BMUB|R^8a!^?5RJ;p1_G*(*d3>_ z`7F9CjbDNbj4{i^qm$kgDJ>Om`hOByIKBk8`J<*eRkuK_h($(-ERfC>m#%4L^U}wk zM`s}Ci;sq>YrTu;Jvz-6FId`6uze|uB+r_~3g4Irmd%ssrgNDyQijc+S>rR_G=h)W zVgbv-SPrVPEf%Xj1D3b+8PVr#ap7GWybe&OIasE6Dq$sKp-7qNc<1>y?tc!=9=OjI z&3#)x$rqC87D2sK9JO3G ?thQgkZ-%L^0pprgUq6_iheGC&ZD;+#jNl#oO%)UM xOIKqNM#_o zAB>?>Ahb_S0lPCbT;f>eX`O-}+>v~79Iq*uJo2MPc6$D4qN}4fk)J$eilS_Er}pNF zxPpGx{hmYdZeX*7;(sc7H*T6m@y$%#vQF&|`?dHrhsxWkZ1=Qa1@8mHrCWJfY?W3G z91t^^OrIrQfeO6g!TD3gJUHE3 ocTKp0ais7Qh-fO7W6b=}Vb!;sugI&lJ5) zo8Nx@M;$Sr2z(;x^V_fgXO0;1pL808_KsP7ULb-7qQCG45`T}FyuXEQ>UZz&=Ze_@ z_F&MQl>VUTY%$Xi{e?S|IV*8iG(2YF+|1kTu}Z2#*x8m)Q0m7eMY+Mn8gUjcr_l4o zfx0UB&_lUlKlgcAiM1n6cx0woBkRR~Cg16XB#~h-aTZCG`MN=S
51pjT*rcKPAv`v3=$7z2jm{8k+}PkF#0C2e zL8FVTI$mS*Rf>7^@4&^6Z{|^2ExEW=z|tl3Kwel(?2ISKr<(mtmx-Hz#y{W8sV-cb zSWxAd@JVm@J*)D0xviH}p72R|_&t;I#+x29X|Lidzkg@a{v~}}L~Foh|K`$ihD}0) zo`{V9Ptf&kN;h2N`#~X?+AZk-scRBPw)o`xnDbg%5<0k?Hc#a`hHDeJx&3@}nKnkq zz;+gqE1OA2(sjltHvG=zg7NAo&g9SeVldxm+#H43M&iauh!tqQhf5bS=Q-4BiI$aB zuH(0~Yk%DHT{!9wvz!JCjrs6km>O}e(R}e1?}5(OU7Dk-;}Q>$DsJ;d4kqOE?C3lT zujZ(YnJ$G(G h j)0=IqLKwmI;_@e2=fbX?cM zZHWO6OqJ=y+Aithg?kqp=X*9eM?WPiqq+N*njfR(7oy-9PFBgfGZl-)+(~@L&-C)M z0&GwVwYodVF<%;-qF*(cDwr)2?S9SAV&%DdCMJU`>Uml27mJ#k`2!CTu@LG}oDA-| zLw|_yWT3__K8x?NhF*|+-__KqQ%Dmizd$U`KPF)RDXZ|d;3_GiI%D;+m@f|MoxFLL zJgpHMaf$1Alkef#A|}t7CNZWW-oXU>6)Y9wqfc4dBkJNlX%36~-p>gO%3A$zyYslL zF~mU)>ab*j`05}oqw{dlx8F-UMbI$gz<&w?E}KH%$vg67qm7!8lO~Wsg<<-9C({R# zN49u#C(m5V8Ll*#X-UHtnK|c6_UP#5OslDLCR>!)NIi|)8qP<==+%_*u6$18mnSe2 zS*7*(ZMm7_>Zt+)Nu#!o;3Nk=x7w5u1dA|O#DmSAI&$)cZ81xKxDvE*a%egE%zp`* zv2$$i$bH@j*Zdt`Zdhm8o^C{T#~=KKwP6;&V!|f!H5gfAfV0>*7=qCdi-oSq)Fd^Y zS+Q@M<2CJFh7}EXQ_gmmR2vxwqr4|8;>zkA8);0P-&vx}-#C#f#A1z)-(O!a?k;Mc z2n{{AB9kHF!J=~vHcx8wZ3OOlNq<`+@_MH0p3On-I#1L%m`rM{Hj6|}&$M8*ty0zz z%{dd&Mr+J+%UwEqoHIoS%_OTeVV0d6Q?yKMx8$Q#waL3Rx@gX0!$Kdaqm5YT6jsfv zJ(c+yTdw-0td_|b3*Fxo>uunLjU#&fQ+QO-eV$WaeJI&o<6Q6~9chl9l7Hv)SS9Ja zc3mGotI=pIk>AqaIh18hB21iL=)iXjX|RCeI+mG|pKK(O<;Tom=5#((DmiF9kjyDU zBg?HW_6!uxNGYCPhYB~cKtFTT`b_8aL5Az+j?rAxe`kbdn*Up)BTn=_rFmY?V%oqF zEghjaHvAK~(b1-vqq&KN`G5RHBiGT$Uvoz!E9gxsc!sEkj;u~`l)rGqjI^)GCn6tc zTfm3?ojc-XwM!OmLpaAP`uo+_Ge?1*+K~@GpxFz0Y--1X<>|tO(@lRRLeLX+Xg!wR zyW%0{P7wGed%&O;Hydm=);oKOxcN`sV`jP+!e@`TcuSnWB0g^v_ ib z9AB~W#1lM)C+-)n$A5ZCvDn44mRx6#Y+aL4xGlaRgyqffcE|j&wsPN)cO<>hyt{lB zWZrau5ge`~Lo3SE;A6W*<8R=+BWG&1S(M3na(wv-8oV;E9(+A=zO8{(Cfs6T&(s$U z6F-uKJJnOqz*@Lro~k%!McpL$XxuDWXo{_Y^IVCnbX_XCaDS`>7G&bpQ HCw!c%EGkR}_!l|FA~_R+wuxcZs)UxsH=Zu=9SJyt6UG5;s~ Hyjh~gxgHO3l4r^B$I|uD8nf_6{vHnzcbPpp z;#TL<5t2Mzl;n-lxg)iHtkTWG5h9K*GWb1rL_Vd5g@2;~3|%DH*W6La%T+SNI*2PC zlBSD{aGE ld&QU6UzU!F l|7 zXcK~W;FOHZ#!n^W9jy{k+VJ3~Tgi8(7|$|ZH$?8(QMyiHJpBO^{Ku3hQ@V!2u}BPz zWO&>J7Jm~S(@1zgX$+5}{P9t4_fH*y_n~M2e*cup=jW!rR4~8=bDPNy4$mE1Y%~gY z&~NyUIrWWM-X1-NvuVu 7vp>8 zkC(%kKfLps;)A~|Ktut)o+H|DCQ!VmILm<;U4Kk9B+FErw!kIVJdaNjFDu;#;faSU z<(rir=Y762SVxL)@}wxlfpVWl%!$O!>J^cMPOxqrsUTep0J9f$Q-zL|{gfW!pY=ZH z&_CZlC9wLCCP8l)`KsT8KZCF6mcC|C{xNMN_&%-nY16V`;5vUyRIYeH_9OhqqxH>= z@P9++Ibzv87BhF81hTlTJA<~9>>YUOt6=*YFYwd3Tnn4qP0D6J>vUHxpuLXeyj% @cmRBTxr(dJ-bNxU7lM3i(i=Zb42T>oi?Sc%2bIBU|~0_98K6Fh(4CHf}9 z?>Y1@;ywS26SO0`Kpei*$T@wU?eX27eM+IM1jpzC 3#uaJ7}08;IA1J#zA+y+;CxlPb&@6 zhYsi-exu3_rQE3cuDjCnlDN!wc_2@^QRS&|*o`VbW$~`lck-?iq`?A;>sV$=et*%H zvU<`lo5z(GBP=gFGq%$fE|x(L^;6l>t;H&Z^}%Als^e{jS7?7}(;9{&8`#Q!DqrGt ziAyPL`nUQ-zN{HxBdw!Lqdh(Tc_t+v?hq$J99Z*wcXM7xgcA|Y578AHr t>x)atC_g$5=}AQwrwNXY#F>O$E8LGC|$ftjh7b*0k`Gjw}AR?>_{4Hf&b zDnF$K?p0v2KZ0eOeUib&%VlF4pvbv2?omS{LP38(XUy_Pu(Ly9e^@xZo`16SnyHzQ zg)bN?qsroSc<@hE-_s^yuy%u#C3&zd2#dQAf>Jq&2mk`4J?}-58y-K~SFD%F({0KJ zpm1Z68;}-eFRPe$Q&qZc^Q3}#@B!a4&7o(4gS>gRV{>^9!O~)%JZ5>;98QrfCx``R zAH4n6@leA7D`vzaZGf^Co_{)H&J%H-2zcmlGv|qhJd5PAh$hAm&Av2Gm~3fh94==6 z!x=d?%uM1Y+u>ePf 6<2nS%KN*zo2ZWOCZAfJTHJ zv2k`10XAfAf |!sC21Rn81W=i3k;b11bh(sLX?-!aYU; z{_WR)M1V~XaWU-qb8W7*dS_D4vvBW-%_J)hlcF=lj6-;Y*1S1@hxlsN7DI0!dUv?a z4$~?eyL;43()lDSzvf+@V1#)_J<79v)Ai9KFToN|{AezTiz4JT}Nd zhrfy{yy0^c~sr^4`=uyV7B@j1n;rgzr&+n81=v0Xu% zl6wjd7d{uyoa0rzpgB1{&y0_r;lefp0h4 <<~P_jd7HwKlanNMh(bJJ=~@{Vi9jUkMEHF>F0xcI!craDT3oZ5 zNQ=HH)2Q4XphWx(onZpC8!GuDBVq(yfhkI?=nPfJz?dh`Pm|!wsLrzn*EJKofzBW6 z5&@x*12}Z!2!9?CIIjagMDMGpC?i6Tgb)na@_f-;WF2p@g^=64e54@@UcB$78NvnA z2G1uptkM@P)9e7va8>5%2?o*&DXr$5fc;%WxDiKD=0d^+^AgWwHj%)E?nB@%kY{*} zOB~$AcH*fhmjibD6iSrLIe1WgUzVuac@dfDCI7vVVe2)Z%1Uw(#Z!zU#VMJj3SK z_@cyHh2pYzhez@`x~_uj*15lLsTq2ns?1G$K&`eC_V*W%3G^OkgOFy2E^Kmo(6t zWQEeT#xaI1Fjb(nUnd7D-6?IB(9OI$P3q+bt^R5UDUEps>puXCi4HSRf`uy6jS=Dq zfFlIPj(>~+Q>9FQplu?$iO7*>&o_)4?^zd4>mC*ER`HLy vS39ibv#;F@k3V- fUGsDWZ--EZT{QEe=6|BH|PgQY@0G zFBw1F%};owxU}4yAUjOVkdWYqC7e#0!%h$Q&7K0;hSvRfEkdgwd}Ab`O%v?)>QA66 zDu29AR~TT!mv7-_L)x%xTYQ&nUR|#?U^HK@BcoN5|BtwLU9#Ia) ?^sRsoCq z57`Tq`Q?{cg+kp+%;@AepYz)U;1Y!6`2xY=w~18&{bzsHenZ!@Vyw_W$nkYp5)?ow z2U4_BWvzpk#$(G#5sHM&S1cOcuvo$9r3d9t<|>v$4)Q^>@p@syNK3J{eVmshbo~U9 z6OrsUpjN-(3oIgNy5WgrJe6>~z7TmP;64)Vnn`!2tU@Hm{FH%hXUZ+4E^t0}rVP_8 z8Tmc7CCY!mx{Ba3E|gPPK%tfEUyYd3{6o=pIKl{#Y^v<9cT6JR@BeBv`WGR6&ZWgt zCT5ChBtp}PeADLj?*6a8P5!#OFLs}rq5msR==_zn((Z~tqZ}-iNH}YRILT=0w~0(f zJDBWb2p!_G4`jlXl2PAj;;w4y0%8C^jzf1ZV!(fYR#l*JgiGNsz``i-A61nobH7cb zF|;L3IeEg2Vjve#Nu;h5yzBFR=wuZ3Uj zcypd&Mw3?>A~6w(barA+l2;is6LL +Gxr~@lG^vfB)4t88mxt%Kg5mlY9sFuLl%v(TZj;sX|Ew z%8$B@A7(ae#2WThY9ose>P}0~FAF%EVW@w7RJ#vHs+!t5MsGAP5O~`XM|uRpdkeIk zN^fZF4mB)yi+ e+HO)xo;Y4 zlhSByu6Q~9&FmeH?E6&r-AlEr+`M=Ee3U$!Y!p}G^05*0zIlX-g*Ou6+II|n^N4@1 z3y37f^?4SX3=OI;g~l_Tp``*XQ_;Wj{3SbE0W|QI9{qB%4)G>8PD*NXZ*z>rAut^Z4US z3rSd-E|(o)QxboK2S(sv!$jg}WO0A-`@)Vlgn04qjL|p^Kw~B`^w5ckhUA^ZKbzp6 z^Qk^MBdiYBGC~Y9BTTa}IzqM7^)PY!&?Pl{8?+}>N~22pz)IE> Zm_RbL(> z$OwQ`0w^jM7Gw_*Xqm5|!POTerl=MQyjSodqHyrehNk#&4&^qvTz}&+#S}hDRVTE1 zAj Loa7PcC2Y%CSCpjd*Yy-uf)G2lLJ`(#g`KvDT7VR z6kwt5Vm}mY@~fG|8=Zu*@ST6|$xYtFhzGQMSpcAw-?Z6H-jzGnhg{s=qY?fd6>)m= zy;KydEI>Qu+TSD6NBEmyl|AHN8uc*B{1Q7saX#~}#Jz<0Y|z2=>9f<|lRv2ZO5ylT zHuZn&ld9->9NMCKhGrEE`*C@g)B0~6OUSDPXY-DEe;-4yffY$b&k28k9cb0T0OV+= zOTi5OnWC9jP5rNb{% 0 z$Q>UeI~NU!w?qZf+nIlbbN<}RtZuN^Kh$y+%1zNf7KLbm-WWH45@^;L5{s&A=2T+q z8zZ9)s+>8$z_-arEAWL<#yf2d0%{gDA2RcWN7?V@ix2RNHo}an4SoRp*D}FLigO p5_gbNDyZep9(>Z>oJn zIfs9P?W-cSo`ixL1q10Ajzo@N!cl&}GmpdUk7u7en-+uyGZA>H#1jXl;7ah>ya9pT z--g3Y)3?h7*TK9-r2irR3gprq^W7E^k~kOqf1QOoPBwq9s^$?_os6fC_6=)+@v{nQ z!vY-zay~gcRwAV%5=?+Fq*NiDNo(f|YZA=JQv8wNeI{_=lyPmmg_uxIXUgYBOsDy( zG48kW*p*u6Qxun_F3dznik^Sq-wdwC`p;)LHe)?BO~O6tp{b*LG{G?&6SHQb0AqNY zp+AbnJU4&Eeg057J_0iiBbwxqax{Fq6xun>s*&+@$}TWS( l(B^Q-H8ZnLLVUxU^X8T*iHx>qFlYqcN zBOu1i`ild9LP)F%!Yfnqx+@cGiSX4gjf)Fc{5XGJn*!RY)K0nf86ciZMBJ`owJw)A znNHJinAI`lIwpC@hQ$z!%M~FexB~9CFRZZpvO8A!Gofp-v> q}f*SI>5Q2TDJQO$2nJPbj{|v0#23-%bZc?LjGmN$E&$w$}=cxh52%V zV>br{GRhoN)Xf3TN58q|ntdX^_!28g_T%QcFS;+j46zI6 Ba$A ze~N~8VW7J?!l@o5Y>&a-JFsPBd+I{;aHl-OZQ@j*qLKMoRuHmo30r*|SdsvuSCOnT zV-*JD7))X;LJUDoyM{j%U!$GZGg88A(ZdOmC6ZV{=ZB#EN+K4$UpWjbh7mrsj#=j6 zc-z~_uhvb=OqDoiV@z&+FIB!Rm&w^de`ZE*2%K us&aTo-eySy8T=?Um^)$NxlSN5`ZDV zTQ9a#3aqwo)^|;@9){ZYki#1LX3fatgJJI`z&$g(T6)-TM91qz1eIw|Vi|EOe-2@Y zKG=(hS|c~*cnF-5cKN=Qi^r2nFd5P)vM{_FV7V#vYneaL<+>`M<3^FTdR2Lr<6e6< zGTdV6((M6>4xL2|@zr~cupD>%6Z+LORZb5@10XR+$x3JEjg8`DdVZ|4vdy5bP|?ch zx1S?fIRYzr&z2C(e9Aug!Fyule{_|Kz)CBA__cDRZ6Ph~D6F*JN1DGj!N! gf1shBpy#3| zLzR@F3=N(U_?SNjs~&0#DCot3Yy0u#{C87?iDQFyorTwrCTHEw;;C4bUEQ3>4_>nK zr*ll8qQy~idD#srLVwl|BP$Mj;3Al4{UFvEp7(H^WSe|n(daHlmq_tOnNmOrGJWU3 za}&e%S7DUmCfErw;pe+Pf0C2W*!Gm MKQQcF7zzsaifH(^>I3&4G)pAnzkJa)(iLl!kJ1wu)`|y+BhiBTH zDRWBZLUzfZTJa>4J??eg)o}_$1uOg9Xm~E+&J4H^?YiiAC+oiK8n_|xPmyHj_DtD} zhe?TeGXxK5X^Nc@e-XZgncXT}Lk;yP415a!29`ao>Do;a($&JoAKqx^T|uL-m7=PJ zEk}sJ3 G!l7+)OVc7rnd zF|iwyX!dS^ {D{gieIX>!hMi_KfT)Qm88G*M4k= z5_MQeii7H85NV{&2KnH1*=T&o{#ybSM#U6nw*w>(UENTtkpErBv!NA}R=0f|C)d|5 z68 HF%coZ%jQ{x^u!e+RefbG9m$oG{!EGoT+~fA4JDVHsfB7WzUORfGBz@Yd5E zR-~V^=lms_8X8?x|AG~eDj>k|vAvYVW|hW-CW-~W1sJfNjDP9Ns#Uz;f#Fgi^eE~E zg5z%;kzP$`{ {qibb@&a~8Bk6b0io|7upS|P|fB%Y1tuq6}nj!_v5SWRfeFdC( zg`6&KKV0A4+@8K)oZl@@-@QA1{qFoG*&qFJR&R`t5x)HL@%`fJ^!n!f`T$SVQpH&v zQK&I~+?=0YFWz3fe>3Wa8n9HLu}algrLsKhe`EPMOEXyTB<|9TNNj0vV|%plMpnMV zK^8dhA>Zh&KJm9qKrjKJ_xNPzUUZ+Fk+=4MTckf9zM~CP*liCZ`cKfs evzbDOwD?KnQ}V14je|yi6_#T^%?=rXD$Rs#QJ* zf1Z$>UM+I=Rz3~V{$meUN91&?kR1$tF+qatuo%WVg0fC-lEb-={k2+n8V8*?8D6#Z z7K~j>wiH5t@z!Y%+AGBvss! 4ndh&^ThBZWxq+qqE7! z0-~`7s NMN6jkaAVG+WI#7Cy $`X{fx3zfuL7q1o}8W5*mbdMZ5 z9{Fx;grqNl%+wf(jpCt_@8nnxnO^)IL7k9~vhiS;o(<4EwEPLEqlqvv{Nn+G@q~#t z0tP2~H)Dtvpq-FyYCMF@Y?J^Me~#KEv5 _ z7;SWbKa?XMO*`&7Wmkk4Srypf)-kixi3bkop$`7KELX`-9$MKC-d_hmCYh*~AGxMY z(VW;{y^NQX>@6j;UUJgZP7 MBd(GIt#D*Ny^$YCr;A z1;_##o4Vdcs-UFT@UIK0-QUX{mb?M{nJ#~hgVwgt+7|cef5rm*Flaqf=q&197@z%& zI*qu6hLQ23$)Vd5MmycO=q0<-X?jg{(|X~69HqzFjpxxT Oh!D_d^{8V_K^-V6MjR$0SzMD1zg+eX$7!y&MoI zi@%5uKIpdIe-`6?f&o65>7Kxi3F2ki^KTF;AcaQT+^CN}0z9)`H~b&VqJaegFY$EH zt~9z98sXLhZAqz{F5fT;3ei0A7B5}NDAojA@hc(QQw%r(DMc~qLuQmPJH_x4aK%#4 z)Lo6QH8tCo>1%#+m83V-X4sw$@jT4idbNl3(>X*;f0)2-+M5b4-MC&A4Mw eX10k_-8#(2K?UCiMW#LyNVV%A{CEtjUzs zt%>2`f1&RAzhO>A(@=MuY(5$QK@PblOaJgn+@Dax#mOw!f@vrwOUlPluAoTB_Ik5S z)C1WLR(dqz!@Gsa(=MBGhw-0Q5wG1FO0BxN#w&MW0=O9TCbi-@6Bpx%^xm}MnMunt z6HX*|(LR=NvShX@@{MXS(r;POG@*k7EDZ?5e*_A(O$mhwl()olX7Uh1c8)1r;^d65 zT+Wkr1BA7T?x3tMJg_ba6q!Thh>WHYMNjcKL@wBIIBqOB7p)-7yDrB_VZ2+MSEOZR zQpD0A*d+|~zy*xzt&I@{)IpdFeruoF45I`7I5Jh_QbrJPgd#$SU2jsAo4~KAP2p}s ze~*QlfDxMjNKYJksWyOdQ&9lqJ~SK9#SE3udQ;!$n>(o!&fLw*D$pLa1mMDYM3A)s z(+KctdGqeW>(h64H;e0wtJ@n5@07!D!78mjLK9UH-6DW1s|(pf?k!XkKa3Xi!s-|I zR>cVrS hf9>eVoy$Iwlh4unC`S6JHu0eM!4$xi#-?tF z%mDWtu?M+RWmv=AT7rccq^E64YG }sQzAv{&eC#S Ij1duc_wFIpr+3#fuD-()ltp_!g3}+@P)zz$Z43wf2m7F zM-f*dm8bwL((JSk &zU4n!(+ARwp&88^{CF*~ zAo-9mE@Xp26&tVt;q3#6QP%gkgnCovtr(p)MsI$I5MUok43Jnk2}J7LnFDK C5UzKoKzt1WM6KGKArN?@<8}x61~MZ{ z3MO-w1aWe%`^H2di<5Ibw?Ukr>&|pbOS$|~sOjSTzTQKN4Xy=5;P2KHF&Z*yc#1HT zEWl=cOJTDOtpt9y=SO}k|Ajv{|00jCnamGR@IcD5a%`Yaj~D@ 5r zz!^&G0#kKmN2_;}l@JI@KoGf&jzAV63XiJZFard_OjFbw#!%3M5ex z z$ZN$SL)pzu5`;L6e{j_L#oEZFHzP!Dd7HALRhG+Ty_cu3My!YGsS_|v!ieRa3g(@l zIW=kAYp1lfNjJ{ESun~5QA+~_I2@a?hfawHM_VJ>t{fCQel3iAb>S{ {+M>I_V$?&(?ZnIzWRzmju zXYuLBf8oZTf8#~Vn1*5>gg{Y$e^mj}WZc~BN>ZAP9=Yp!vnr}h{!;IwZ?R?b2EV++ zFK!A_fzc-Jb68S4@{F5QWXqH-KIvC|z~R$wQ{z%EnC(}kM sOwe;P|g_ImZLwExrx7G<#~LoAch z=iQ6yVBF!>SR|#-xfd0UFh6y}x;0Q3dG(!!@gFw4zZt250?8`?nk7I03M&6;2<=v) zHAFIN1NB26x$FvP_sfVcFe0%Vc?E)vYV6A(GBC!0hP;h0H+o4h52qdjfufwms>fBJ zOiSJ&f1rz5H0SxF#!CX4#x2daG>GOrf*^lydHy0y5~mAa#}nnC?NG(r+sk(Wa+-}7 z7tx|k5 ^PACD zu-@8`0doEoZweluOC5%>BkWx|1Pq <#yvTN^Y?>wUDF`fwvNa|lE}aCQ z{EG18G>A1Gnd5l=8m0fJOx?gZK)A`fln0z9a==PF)--qX3&327hq+o7J8qY^FVzx; zfAyh*J~EdmOit0{icI3B6g_18TPD7xa2X&OCdfnrACuV+!e1VFC0p+yq-x gWb{C!jMVBUu+;BpXn}d!L<9<&Vp!u zGiwRR%I@rDNOWra?$ZYk^uLu=SHMtDfB97S Wz~v4BFRt7`oAMz| zs4x&Z- )Y2hxT*!KM z=_*=`+LVXmB8MoFNHp|fBK^h3F`^CEE5a5#I?Xp#5H5xUb37s#3$I4g)S@$^hOu|p zP*@fvjsq>}ZR*;hCG4rI!!D&g*TJrZy(l_HZ076~5pU#%tQZlil7E~*e}}}8QqCl9 zk{Y>t|8xZ5d5LHxp)T=d7 zuS1;Cx@lz#&Pf~l5ZVJYD@11QeQ3E}4_Bntwy3a5kt=sPJ$th%cH&2V=ip?+U0uRV z2;_+{pX`vJWr|kIQ?yWee-tg i;4(g%G=s8}oDV-d;igF%JO>Czqr7=nuvSm|;ahd6sxn2M8E>05}g;F&>>4s9i9 z0#CS`SRsh53W#uS!KyvvUK#76;+@jXMBjzAcK=NeE<}xS3Pt}rR5+c?V9X5TTN^CI z3xkvo{K4BdMWVYffBLB`KN&Cv9nGZRep6n`bpRonO;Oi<#AJRYli4wZde|y{5Me8$ zkeZl*JdcRDvAhB0L7op>Uc~!KZy>d4Cxup-;yhn=Z6O6H)e9c19CPhKQuUgKsYUD; z_Rs>v!>L~JQ<8$=gs(&y&iIO7w5IhQ*63ik)%FCxQ0!ITe+_?w>EB@zQJWYD8UCEn z&zeLWdZ6}(sQ5>)_m={n!Zrvz0$`Qhzl;o7&_N08R>H{>6ZDQ?tY?spFxmZ!8pF77 zQx1kRaNw`X#l+#?@dHgCVYNpUx9<>6rF4j4Od#PleVUn8KnoAUh%nrmjnnE4ZwGBc z!{B~Vw7(nXf9wsAw+`eHF|LcrVw@nZ *rti` zqX)lTN8|FO+ji~75j%S2rqkrxEH?DYy~Of%i_N?@e|^}H2&EzU?pyY{5RizQ0c?Z_ zx!GxAe%L6zToHoa_9;?i9hOB>mpiB44oKX}sEVIlax8O|MbvwUEH^y`$(dqHP`T05 zkdh6P%Z&@gohgy!#x3ixEE<#>x3G~6qqC?8KwQsK>PedNM0+`?#L)iM1MHWf9GR*^ z0t|&Xf1wQ}{Rj+>U=F@k?B&4L6V;&|hIKI7wXg~fF6j; =%#XiC=jxarQ z;5mtvzF0bKoAIBrOFy(6oxuHF)@_O9k_fnUf6Th+w5`#D?o?y%j8D&IdqcdL*Ec?7 zrw$#f>vQ{ POp+0(ZS ze^Sfpas*foT !NTQy!_HmS%C%L(K+q2v6b~H2vK8%e!X)I}m zvbEWbf&- K6Yv0sil7y EE!>2kdQBP^ @1` +^4(du}@qwG)h%YX9UK)8Fz7;`4()^OP zBKn@Ry|N%fzQLy~o=+JfC |Kp z!NAUCB0EUE1kv~^HvJIaD+$LEr^v8zXe#7 j%tQ`pb>i!k+WPDK0u z&P2U%+od96;&ezIF@0*0n87RPfAAZeMsK4D%@7s@zzD#MWN~I7dxYSEc1rdgZaDtV zxtYhS1DDGaKbq5i9_V}t@;`q6Pu!sV$M65ZwdNijqwkj?^~j5606#MK=O(bG{eBrt z`wd8v3M^Qo!6#v bCT8DTQI5i9(SiWE3?AD3d%+BQ10VNrW5A0MV zGsdJvLtf$1__R2fB9jI|9lYk;&;$wm`cSU-v_aq`QH{o777mwWP%JD8hp^{X1R_1r zAAM_KdxAdQ+)#QkHlgh0e?nNE!bS}8(}@)wu3POeE>N4O01;S+YhgP^2#X mrh6B6WE;aw~NdmJE!ahjS@@~|Am+>nybV(u|2VnahK+2#caDV(9VDgj_ zGblkHHN7gjyxgQZtWTVl_P2e+ApgEPpz!pJKU!adv@lX$x?hN4O2hE4>f?~=K5shQ zSPp;U>cZEUR&L#a&$14DZK8O&%2mOe&+xjfcFT+|j{9e_A_BhMIReuN7Yh0P)j}it zmBimsi$1<85JbB0meoU@K!2r6EgIfhI9@tu _~>)A=B?JmyT?t$O?$(QgZJvO z*uZWy&w(p`3bIqjt-K|eOLfqSlWfAHt4&59S6n%;<2{)icYlgg228nmHV~ofJ9uf+ zye68_+X$PpV9q65?VH30Lz*&WdL_Zm_D%2s`E6g~vE97s70-u8jEw$*05|a2$AKaC zi_|hoR-%|*6GX72LICfGM!RMxSO1D}z1{6`!feVlgda=PF3g1ZTLBVQPl?h=6uuW) z&-Hu4r29Ga+<*S?xNsc|-N_1L;z_qd8vK?1x^TrN+HFzoPd?|X)!p*3E| qm9S${@(iogC?=rfp;_6fk7Qh(I711( z#kS#H%EWm}0Hd1nr!Md@MHDkGs%JFRLGj|qm?FgaI)6zvr`&apLep7IFujExzKt=9 z6YF>-UB%Vw19EYIokm#nAjc(C%gw%>NUm%b>f {Deo;oOHBUT5I0XrWq4h zJQdALhs&jwd6nH4yq73Dt?3dq8t=7L?4Ebq)CL+DcJ@+@$$Z#vHd(vBr(y>!*
1#J5ERl ;eP-#!>hl930yI{Pf9K3?VB-KMU;e4Z=~5b=lu0%SRCL9D-su#0rS zfz1^um9Avs%Ji(WfORzbOeBm`Q`fCnV-1n0CTbxNSjG(?3k?e4tzvYDmeQND%Sz zlfwyED#~S4VW9UCZ*6qNx^Bv;H-=!C0rP=?7wSrk8XPAu4>p-3Mxv;YF*g$SgN_t4 zCIz~!#Z$zq$s+ MTS3Q;)gLl1i7TN zdi#;6CM^owdn70pCdHVvxWNF2lHLnrj3?!04#`sPsEW^fqCbu@%7n7KDntF#2!B >&?&exWoMg@p;vaCom^+Sjyw!Byz5f8M) zPkNXwl9UIz$uU-}+_VW(lufZLcYouJ8DVMd*o0Y1X?{9D4A9FDaqEz5ZfPsmA}wsJ z0PCtKV7APj@T?yu z=w7dQ&+Y{NfjFJxGjwM>rHq!kESRu4)Ak+))AN&P4sU+>903g!*K&!+t8Cqy*?fG$ z9I@zhC^WxzqIx7m-6%~NzJEK#HZM06KI?=&!#3{~Md6BjGn}It2Hg@hkH#Tn`Ib2M z5QJqVJQD*c3D1K`iR%YXQpt=}hpwD{%2&MRhaEg8#^*5-ol?G4wCVZNsr($~^X^_6 zAyand<|RK(%AM*{rQE5fcA4`RdtGmNa{}UjXS>I8Q@8c*@nw`(*?-D51OPAt;EW)z z)RnLT<8vyvX)1oa=AXlSNtxUuoS4b{<=)h*{c;+mVS8bG2~%$KFIMQl19F+5bbLY+ z>e5dW)<=zQ$ViV6YMr+{i3DY#K)7{zFdzsqhx z@%Oq%t(tLr=;cn}mH-R^ZY11C{%xE+s=T>agQf1V$bUE8BkXrPp^vcJbA;tB*}Tph zxoJXTWb_m4Uky^l_|j(yy20}iO4&Bwu~peZoA)Gdmt~ppE7|ujKB+e5Q}`}^6T1iY z|72>?o8nmD;Bc|6e=XawofQ{EdC4iyh*HMI=e(!{2214$^OBY!t)6RdRVqfb+3C&V z;v%5? QrmkS`4Da-A?sMF# zG=onB$`t5npC( +7(z{sGGOfun$TEWB=(`}TX366>@*~6>+)o-t6@km~N;E%#@nM{@a^6K=}UtUE^ zEPpn|6Rr-+vnR3L2)2Im295kBqpe$q6sL0mC5!%(*y}`0N&};vUV`Z~O3(yHWsKxq z&Kqt}d|^!SuxGpHpp4Lr4Disvh^Btfg96eWQXT%{YiXR`HiIK-r~#1Gv2r>dr$snH zxZJD_^#F{Rh}I8BZL{YhtBPVZiyLyEIDgav$V1IE;9<`NL!L#1m~ Gk`I_iwW6^Eap0XYlFfeDU%6;`VQ&GWF^!e2c(NxORd*y{^lEIdy@jDw;vfP)V!` zjK~g;(dV=-jzbMDTjWT32eGDr>!ZCMp^Q4XZP)CVIH3+uPxUgt-*4dT^JIC{;Z=C} zJ}~fJ2kFMdf@61> =@R)vk$#M9OmR0-d#>K*_4B0H6qz+bjXn=DY zaEyOlOlU=b(YhFIHzdryIY<)b-fY^oC?F6sR1Xl0Ih~mUKy+uQd=TKq1{~#=yEbVn zCbrWk8f3Z>xg(TzWy0%LK;8IA#?;YvA&9NQ>wvWWv$<6WqO}mft$z&|!+%JxNaY0C z-&$~^ `Sv^7>%qyis_>}Y76Hh+_KUBS*EK1>ECMlf1I^l<+U}d>lU;$s^-nW-LrM;p8 zm#7QdiwgH`WS1Jx%YPg{5o&VAPknCl9R@fl8ubd(VXWY$!K5+oK#T^sywpH)P^lNy z3Q7h}v?JPUCrO+@9WH9JDicD7oQmbweHKto(E=tb(H$em9RX%^i@2qTKai-7M!9hp z3-37V6&8mIr6G3QG{bx9Y6XSX>>-YmubwvEraM5bdhi6yg?|kjrv-waRc0ZbC`RJQ zX8@LUYv9YU9JSfDu8L>@*49G?mSXo|_Ms}G*t+L>A{$srw%g}AhOM1O71~p2kR-H& z`$P$iV1p2DvG| kAO KV#fjgmO%AdV2MONS?0 z3?UfT#)x%FdyK+_kRuaD;;@dCOzsOv$?5%9Z1T2jxm~qstY0A<1h-sp%R`Ab#X4WU zWa3KP-W8Vu!eKp}cZ9yMt`0R-xQLjdjPv66&sF~A%YQy=%MBdQvMsyEe;!c;A<5Vq z2_T**0AU75_Q&u4`#mDx=!<{+{(naGh CzM_r|+}ttSQI#?)0l-Ys@XFpTxbaZ1{r|G*Q&t`ZUE?uDyv_M1uf zM(nB=h=0U~h>Q?LK!9Ha }#t)QYRIzcSfSJ!zGS@g+z|7#*JXyfZ*)~NS??r6pPzE?d zS=WFbrzxQhcS%X8!+XRVI_D&vQW(;oQ5t+p!hZ fi#Qp?U=ZU#aX9aHJK8hQC?( z^NBTqx;jA_;q=h6YeL7TUKxyPqSFX)ZeetQ%%uhGY3c&)4YYCY7qQW90l$S%u(?enjJk%DHXu=6+LnKbeiyk@8md1BT1N!P;{HTTJPaTL)m+NEL(^l zSKmME+of uG_#S z>ZZzQW|pp~7A!2Xk61~1tFYwcWmAbMV7jcD%?j{#l+z_yY@y>nZ&q*yz&2tDq1uo4 z39Qs909$zmz}CHXutz2v`)X$(UY &6@#YeP=8W2L zZxPDZn(OhVycit3DK7|{xR?JJTX1}RkV}INjBBfKg^+y*FkYd14b{FXzlxdIl>GRp z&5|?2q=e6GQ+_Fog`+Q{jn4tQf|0YzG+CE@RPYNEeyHgg*S1tetB(F|W3u6sGP4eX zY_0RZTTeb1oN;aLK7ZTji3kP*cHy9wAZc*}%6&o`gh`MKCw5p)d{G;Sh%addVa!Ny zpD8#PY+&n;apOvT9&InjkUsShhx}h({=RO?)k%I|?>k%wxXZi8hq4-9HTLg+`;Rx= zR80DVJ+3S?>`xWwmAdGcaKEXSUtmR4S&c5Twz!2MBg5AwjDMePRXpVTO_yzo_4quR zMJFtousjn$@9=AokTwYARrd8|yq@lJ(&nSQVq|<~!2SE*oU1oAbIeoP;~Sawk=U~G zh }Q^P|vRJkG+$1izTp{rxMGd)fLgiizxmKs3kAsW9r zJMdI;hTGw-$bbBoA*7$5rk3)m%c^=+{0Pri7TpK4q$nf?Nch|`1L-126c8ByHn8Qa zej0fLXojw6n*FY0$(2;!^uqog!wI=Dk&{d>?C*X~s9@47Mp!m1tS%Kmm?YUH;2 A)ZfbbhNA52l;uk+#>)K_6R!j(_-BR|R{{U((;4!cY{+4<<~2 zE$W S3eyXn6 z6i`?4wpKEowiT4O8tmN&Eq`tOeObeLsuV*0Dil_*0OvpOqn~8IV>tH-$Eu}g=AL4y z8pa<5-hY|8 bPv$-u2I5qiB9JidxWE~}KA&qd#vu~3LjRSeT2Q1rE;vCsz zCV6u1(tC2Q#UswjOr? ;n4=!5Hm&sMTMEhE&`=lNI_1hA`+=EGz|PR0xb97C&Np_On(eh z@g~cp%2IE8&ooAu3O9qN($I$Tt$QQ61+O<{r*atE# b#taWELR*=?Me3Zy(SZ2oOAe&y PwkLVEt(ziy*zh+mjNxOjP+Yp@ zWFgBlZYj^{0COhIDfldzpL&s9GInKPu5`e D7fjF>YWY*MDz&?}o)bs+s<^ z{_H}Q0V=WU0O5sjX=J^OBvM1khB+eZ(3b<`kc_(8yofl2C8pl%1)E8N07wKPEK!jG z^g4nqBoZvpN1nC$X7iG!3`Bu7lh&_&xMLa|c?55LD1qsB(4AK}2@8ePzNZ0pD92-W znGQ9qY7W!ji(*waX@6?z^h82n65(|S-=Rl8$8mY3s|2V=m8}z5U&*>!O+QE977Mbg z)r2zwEPCj2w)xDR?J~Z9`@-+j*C=`!&0m zi$eq(X4qW5+}s)%4i?z|69tV4VTX_dq%Bv4?@a7Ij!b()e_d$xzFG3-O^G`@ +Zp=gR~N%77h8t`tDFXKD`0Hy)( zxXY?w1n?2iuifYlF}$sF&) +df2qVO3XSrQSiKtt_rHdAUbeKSMK1 zU89&eaDO~a%$?%a(1O#}_^fejx_NIo_-(E2itLnw^D;vB(-}yIH^_gKEkgd8c>Zg~ z{)=%DJEHA=yB!=YD4L6mx72kfRHlUH`we}z(A+_G2T&^6LWa$?L1g9(ZkPkfOuI0@ z*_g0$h`(*r0<95POwM%jMbF*>U*-JG1N&!|Fn<8B M7@M1PHQS z!z!O2M#v=`12{%-SPkLOgG6HjEjvqOx1_H(^*u!7;^lBtz;uq-wm+6ZWXF^pQ+98B zh!!t)3;E4^DAyxQ*xM8-FSV70))slQe2l)kl-#(xH$8hB{E@e;m-}uMRi(R;pDY~j zL4St>Bs1l$aXz7m2IE`haL3aCOQ-uNKm8Yd-dWewt3CRBvdpWj 3`eZkKF;;;KQ1yDv oRqu&Vi6;Ds5KJd1%)rQn1tr_(%OK&ATQ_kY+P;DxXc7hWHH8HEINPA#n= z`BTQ($}Hc)1kXkcIBfV+RmEom<^Ua5D-Ph}*-^55%LFF)OPCj?f-9}y!lfYS#DDfh z9Uj>2A0F_P-S-1euJ8US(Ua|qI!6$Y@paz!h9mQbMY_YaT=lhFxV5-_!sYNU^`1_X z=cy5v|Hb$o*miL?6#Ty6ig=EQ=f;C#bdCQ7L~0~S#82boGW4EDJk%CEJ)V`5RP~@K zsKRr25a-hnP13%~ml$8F^|qE&3V*v^QFd$qm4X=t53=0XQ4EmGgN_IeWZnwP*ZsZr zPH>UZgD-Jh6x1?_V8Dbv0z%RF?k1-REK}Hs%5DhtYPnrGBOp0Zbz(OGGtk^zyS!;* zsVJ?VNnp8&+WrBXJ{F$PPXQ=i{G@!->4R*c+;g4K=cY45tPF_+j)OvmXnz=|5Gg$r z630&=AAtlaVG*X3Pfw6~wJoa@Vn{zmi ta<- zkfd$nR2f_$VX)9PMGT}L6Mul10a99Vx=ElF79+$GH$XicUkg{yJoyMIA_f?@`>*kD zo}tkQyMZ3i(7v<@Z6Al#hzb)wEMpKPYD|F&Tpc0@)BrDxD5N-!=|@-zGeA1&S&x3m zy-)XupANVBUbJyIJ~R)eXzh=}%5*Mdg)smh=`Wb6oWR4Lf@8g$jem#<`e0-=z LmGF_Ze`dfPFL<;j zi7-!-Ijm