Spawner

The spawner is a system for defining and creating individual objects from a base template called a prototype. It is only designed for use with in-game Objects, not any other type of entity.

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

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

This is what the spawner is meant for - it offers a simple way to customize instances of a typeclass to make them unique and different without needing to modify its typeclass.

Using @spawn

The spawner can be used from inside the game through the Builder-only @spawn command:

@spawn {"key":"Orc shaman", \
        "typeclass": "typeclasses.monsters.Orc", \
        "weapon": "wooden staff", \
        "health": 20}

(The command is all on one line, it’s split here for readability). The dictionary should be a valid Python dictionary, including quotes for strings etc. We refer to this dictionary as the prototype. Each key of the prototype is parsed and used to customize the creation of the object, much like a blueprint.

One can also put the prototype dictionary in a module. This must be a module included in the settings.PROTOTYPE_MODULES list. The default place to put prototypes is in mygame/world/prototypes.py. Here is an example of the Orc shaman prototype stored in the module:

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

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

With this prototype in place (and assuming you actually have the Orc typeclass in mygame/typeclasses/monsters.py), you can henceforth build an orc shaman in-game with

@spawn ORC_SHAMAN

or the equivalent but longer

@spawn {"prototype":ORC_SHAMAN}

This second form actually creates a new prototype that starts out identical to its parent. This form is useful since prototypes supports inheritance; One can replace any of the keys of the parent prototype on the fly:

@spawn {"prototype":"ORC_SHAMAN", \
        "key":"Orguth the terrible", \
        "health":40}

(again, the command is one line, we split it for readability in the wiki).

Defining the prototype dictionary

The prototype dictionary allows the following (optional) keys and corresponding values:

Note: All keywords below can also be asssigned a callable rather than a fixed value. If so, that callable will be called without arguments for every time a new object of this prototype is created. This is for example ideal for assigning random values.
  • key - the main object identifier. Defaults to “Spawned Object X”, where X is a random integer.
  • typeclass - python-path to the typeclass to use, if not set, will use settings.BASE_OBJECT_TYPECLASS.
  • location - this should be a #dbref. If not specified, the object will be created in the same location as the one calling @spawn. Can be None if created OOC. Note that if you use the spawner functionality in code (see below), no location will be set by default.
  • home - a valid #dbref. Defaults to location or settings.DEFAULT_HOME if location does not exist.
  • destination - a valid #dbref. Only used by exits.
  • permissions - string or list of permission strings, like ["Players", "may_use_red_door"]
  • locks - a lock-string.
  • aliases - string or list of strings for use as aliases
  • tags - string or list of strings for use as Tags. These will have no set tag category.
  • prototype - points to the name of a parent prototype dictionary. The parent slots are used if not overloaded in this prototype. Inheritance will recursively follow the tree until it comes to a prototype without the “prototype” property. Be careful to avoid inheritance loops! The prototype can also be a list for multiple inheritance. Multiple inheritance goes from left to right, with rightmost overloading leftmost parent. The parent prototypes are pre-defined as global variables in a module set by settings.PROTOTYPE_MODULES (see below).
  • ndb_<name> - sets the value of a non-persistent attribute (“ndb_” is stripped from the name).
  • exec - This accepts a code snippet or a list of code snippets. They will all be called after the object has been created in the database and are intended to be used e.g. to call custom methods or handlers on the object. The execution environment has access to evennia for the main library and obj to access the just created object. Note that exec is only available to users of @spawn with the permission Immortals - the execution of arbitrary python code would be a severe security issue to allow for regular users.
  • any other keywords are interpreted as Attributes and their values.

Prototypes from modules

You can add new prototypes by adding a new prototype to the world/prototypes.py file in your game directory. You can also add more prototype modules by appending it to the list PROTOTYPE_MODULES in your settings file.

All globally defined dictionaries in these modules (that is, all dictionaries assigned to variables in the outermost scope of the module) will be read in and interpreted as prototypes. The spawner will import them into a global dictionary where the variable names are keys, and so they can be accessed by their variable name using the prototype slot.

In a module the prototype dictionary can be more advanced since you are not limited to input on the command line. Instead of just giving #dbrefs you could import and assign objects directly if you wanted to. Each value can also be a callable to allow for dynamic allocation and look-up. The callable takes no argument and should return the value to use for the slot. It will be called every time the prototype
is used.

Below is an example of a prototype module (we assume a Troll typeclass already exists in typeclasses.monsters):

# a file my_prototypes.py

from random import randint

# a base troll creature
TROLL = {"key": "Troll warrior",
         "typeclass": "typeclasses.monsters.Troll",
         "health": lambda:randint(20, 40) # random value between 20-40
         "attacks": ["fists"]
         "resists": ["poison", "cold"]
         "weaknesses": ["fire", "light"],
         "regeneration_speed": 1}

# A troll with a different weapon
TROLL_SMASHER = {"prototype": "TROLL",
                 "key": "Troll smasher",
                 "attacks": ["giant club"]}

# Troll magic user
TROLL_SHAMAN = {"prototype": "TROLL",
                "key": "Troll shaman",
                "spells": ["ice cone", "poison dart"]}
# Boss creature
ELITE_TROLL = {"health": randint(40, 50),
               "attacks": ["fists", "charge"],
               "regeneration_speed": 2,
               "exec": "obj.growl_menacingly()"}
# Magical boss
SHAMAN_BOSS = {"prototype": ("TROLL_SHAMAN", "ELITE_TROLL"),
               "key": "Master shaman"}

# Shaman with a club
TROLL_SHAMAN_SMASHER = {"prototype": ("TROLL_SMASHER", "SHAMAN_BOSS"),
                        "key": "Grunt the bone crusher"}

See mygame/world/prototypes.py for an example involving goblins.

Note: how in this example ELITE_TROLL has no prototype of its own. This still makes ELITE_TROLL useful in order to decorate other prototypes (like we do for the shaman boss). But if you wanted to create an ELITE_TROLL on its own you would get a normal Object and not a Troll since prototype is not set to point back to a prototype where the Troll typeclass is defined.

Second Note: In the above example we define the base “health” of the TROLL to be randomly assigned. Since this is a callable function, it will be called every time a new instance of this prototype (or its children) is created, giving each a random health value.

With the above prototypes loaded you could then use @spawn to easily add a troll shaman from the prototype:

@spawn TROLL_SHAMAN

You could also customize the prototype directly on the command line:

@spawn {"prototype":"TROLL_SHAMAN", "key":"Gorat the wise", "tags"=["evil", "quest-giver"]}

This would spawn a new troll in the same location. It would get the custom key but otherwise be initialized with all the properties associated with a TROLL_SHAMAN (and, by inheritance, that of a TROLL as well).

If you wanted you could easily expand the @spawn command idea to give your builders custom create-commands based on various prototypes.

Using evennia.utils.spawner()

In code you access the spawner mechanism directly via the call

new_objects = evennia.utils.spawner.spawn(*prototype)
All arguments are prototype dictionaries. The function will return a
matching list of created objects. Example:
obj1, obj2 = evennia.utils.spawner.spawn({"key": "Obj1", "desc": "A test"},
                                         {"key": "Obj2", "desc": "Another test"})

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

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