This page aims to provide an overview of the entity system on both the server and the client, and how they tie together. It should be useful for anyone who wants to learn how to create and alter items in the game world.
- 1 Introduction
- 2 The basic entity
- 3 Entities on the server
- 4 Entities on the client
Every item you see in the game world is an "entity". That includes the avatar you control, the coins in the avatar's pocket, the trees nearby and even more esoteric things such as the weather or the terrain. The Entity is the most common form that the server uses to keep track the world with, and it's also the basis for much of the communication between the client and the server. In order to alter the world you must understand how the entities in on the server corresponds to entities on the client, and how media resources such as meshes, animations, sounds, textures and so on then are bound to the entities and their data.
The basic entity
The common base of all the components in the Worldforge system is the Atlas Protocol. This is a protocol which allows us to express both entities and actions in a very descriptive way, thus allowing for multiple client types connecting to multiple server types. When we talk about an entity we will always base it on an Atlas representation.
Entities on the server
Form coder/world creator perspective Entities are composed of two things. Atlas operations handlers (check: Hacking Cyphesis) that are responsible for Entity reactions to various incentives and Properties. Sentient Entities also have "mind" attached but I have no idea about that: please write some info here.
This paragraph covers the properties - which are game objects that represent Entity attributes (mass, stamina, bounding box, etc).The information below is related to C++ Cyphesis code and python interfaces to it. You will use that knowledge during coding. The bold font is used for c++ classes wheras italics marks a path in source code.
Properties are the name given in Cyphesis to Atlas attributes that entities have which encode some kind of server behavior. All entities have a map of names properties, which inherit from PropertyBase defined in common/Property.h. In addition entity types also have a map of properties which are the defaults for entities of that type, and they inherit defaults from parent types. PropertyBase has virtual methods which are overridden in subclasses allowing changes to those properties to have effects beyond simply modifying the value they store.
In general properties are handled via PropertyBase (definied in ./common/Property.h) class. The value hold within a Property can be anything but for purpose of sending over Atlas it must be expressed as an instance of Atlass::Message::Element class. The Element may hold a single value of type: string, int, float, Ptr. The LocatedEntity (./rulesets/LocatedEntity.h) class holds a map of properties for an entity namely map PropertyDict. This map "maps" property names to class instances.
When an Atlas attribute is assigned to an entity, it uses the PropertyManager interface to determine what property class should handle the attribute value, and this interface is implemented by CorePropertyManager. You can see all the types of property available in the server by default by looking at the constructor for CorePropertyManager in server/CorePropertyManager.cpp. Properties already have a way of including code which can be activated by operations, but the mechanism is currently messy, and cannot yet trigger Python scripts.
As world creator you will find yourself accessing entity properties in python scripts i.e when creating task. Cyphesis provides python interface to Entity object, you can change Entity properties this way. You get access to entity interface by including atlas module in your scripts:
from atlas import *
Entity fields that exist in each entity:
- entity.type - type of Entity - corresponds to <id> element in xml file (to be found in data directory) defining Atlas entity subclasses.
- entity.id - a reference
- entity.mode - related to current game physics, a mode of movement. Possible values:
- floating - for things that float on water (doesn't work btw).
- fixed - position fixed, doesn't care about gravity.
- none - gravity affects the thing (atm simply constraints "z" coordinate to earth level).
As you can see properties are mapped into Entity python object fields.
You can see more examples what you can do with Entity using python scripts in Task checkout: scritps rulesets/deeds/world/tasks. What are tasks is whole another story however.
Entities on the client
When Ember recieves info about a new entity the first thing it needs to do is to determine what, if any, graphical or acoustic representations should be used for the entity. This is done by querying an internal rule based system called the Model Mapping System. It contains a collection of rules which are used to trigger different actions, such as showing a certain Model, or turning on a certain part of a Model. The Model and the Model Mapping framework are closely tied together.
A user can extend the predefined Model and Mapping system by adding new definitions in the user directory. On *NIX machines this should be the directory ~/.ember/user-media/modeldefinitions. Any file added here with a suffix of .modeldef will be parsed as a Model definition, and any file with a suffix of .entitymap will be parsed as a Mapping.
Each server entity is bound to zero or one Model in Ember. A Model can include multiple Ogre meshes, as well as particle system and sounds. It also contains information on how to bind animations to actions performed by the entities. An example of a model definition can be seen here:
<model name="graniteA" usescaleof="all" showcontained="true" icon=""> <translate x="0.000000" y="0.000000" z="0.000000" /> <rotation x="1.000000" y="0.000000" z="0.000000" degrees="0.000000" /> <submodels> <submodel mesh="3d_objects/environment/boulder/models/granite/graniteA.mesh"> <parts> <part name="granite" show="true" group="main"> <subentities> <subentity index="0" material="/global/environment/boulder/granite" /> </subentities> </part> <part name="felsite" show="false" group="main"> <subentities> <subentity index="0" material="/global/environment/boulder/felsite" /> </subentities> </part> <part name="felsite_face" show="false" group="main"> <subentities> <subentity index="0" material="/global/environment/boulder/felsite_face" /> </subentities> </part> </parts> </submodel> </submodels> <actions /> <attachpoints /> <views /> </model>
This is a definition for a model called "graniteA", which is a stone/boulder type. A single mesh is used (the "submodel" part). This mesh has in turn three "parts" defined, each uniquely identified by a name. All parts belong to the same group ("main"), which means that only one of them can be active at once. If any other part is activated all other parts within the same group will automatically be activated. This functionality will be very useful when we later on look how the Model is tied into the Model Mapping system.
Each part defines one or many subentities. These corresponds to Ogre subentities of the mesh. In this example we have three parts which all three uses the same subentity, but with different materials. The idea here is to reuse the same boulder mesh, but with different textures, thus providing more diversity to the world.
Each model can contain many different meshes. This is accomplished by adding multiple "submodel" elements. Worth noting is that if you have an animated mesh as your first submodel, all meshes in subsequent submodels need to be able to share the same skeleton as the first mesh. An example of this might be a character mesh, where the first mesh is the body only, and the clothes are defined in other meshes, added as extra submodels. In order for everything to correctly animate they all need to share the same skeleton (which Ember determines to be the skeleton referenced by the first mesh).
When Ember reads the model definition all parts will be combined. So if you have one submodel with a part named "main", and then another submodel with also a part named "main", the submeshes defined will be combined into one single "main" part in Ember. This allows you to bind submeshes from many different meshes together.
NOTE: This section needs to be updated a little to reflect animation blending methods.
In our granite example we didn't define any actions, since rocks generally are quite static. But most animated Models, such as humans and animals, will have actions defined. Whenever the entity to which the Model is attached to performs an action, Ember does a lookup in the Model definition for that specific action and performs what's specified there. In most cases it's suitable to play a certain animation, but it's should also be possible to play a sound (currently not implemented though). For now we'll settle for just animations. An example of an "<actions>" element for a more lively Model can be seen here:
<actions> <action name="__movement_idle" speed="1.000000"> <animations> <animation iterations="6"> <animationpart name="_Stand" weight="1.000000" /> </animation> <animation iterations="1"> <animationpart name="_ScratchHead" weight="1.000000" /> </animation> </animations> </action> <action name="__movement_walk" speed="1.000000"> <animations> <animation iterations="1"> <animationpart name="_Walk" weight="1.000000" /> </animation> </animations> </action> <action name="__movement_swim" speed="1.000000"> <animations> <animation iterations="1"> <animationpart name="_Walk" weight="1.000000" /> </animation> </animations> </action> <action name="__movement_run" speed="1.000000"> <animations> <animation iterations="1"> <animationpart name="_Jog" weight="1.000000" /> </animation> </animations> </action> <action name="taunt" speed="1.000000"> <animations> <animation iterations="1"> <animationpart name="_@Bash" weight="1.000000" /> </animation> </animations> </action> <action name="attack" speed="1.000000"> <animations> <animation iterations="1"> <animationpart name="_@Bash1" weight="1.000000" /> </animation> </animations> </action> </actions>
These are the actions defined for an older version of our goblin mesh. The first four actions are the ones that are special for movements. Further actions are however directly mapped to actions as defined on the server (such as "pickup" or "attack"). The movement specific actions are:
The __movement_idle action is what will be played whenever the entity isn't doing anything special.
The format for the actions is pretty straight forward. As mentioned before, the "name" attribute is matched against a certain action as sent from the server. As an example, whenever an entity picks something up the "pickup" action will be performed. For each action, a number of animations can be specified. Each animation has an optional "iterations" attribute. If none is specified it will default to 1. This specifies how many times the animation will loop until the next animation will be played. All normal actions will stop playing one all of the animatons have played once. The movement animations (those beginning with "__movement_" will however loop back once they've completed. So in this example, the idle action will first play the "_Stand" animation six times, then play the "_ScratchHead" animation once, and then loop back to the beginning of the cycle again.
You also have the option to combine multiple animations, though it's not shown here. In those cases the "weight" attribute will come in handy since it can be used to tell Ogre how to weight the different animations against each others.
The Model in itself however contains no information on how it should be used by Ember. For that we need to use the Entity Mapping framwork. This is a small rule based system which Ember uses to determine how any given entity should be presented. The framework reads all its rules from an xml file, an example can be found here. This is an excerpt of that file:
<entitymapping name="fern"> <entitymatch> <case> <caseparam type="equals">fern</caseparam> <action type="display-model">fern</action> </case> </entitymatch> </entitymapping> <autoentitymapping name="carrot" /> <entitymapping name="boulder"> <entitymatch> <case> <caseparam type="equals">boulder</caseparam> <action type="display-model">graniteA</action> <attributematch attribute="style" > <case> <caseparam type="equals">a</caseparam> <action type="display-model">graniteA</action> </case> <case> <caseparam type="equals">b</caseparam> <action type="display-model">graniteB</action> </case> <case> <caseparam type="equals">c</caseparam> <action type="display-model">graniteC</action> </case> <case> <caseparam type="equals">d</caseparam> <action type="display-model">graniteD</action> </case> </attributematch> </case> </entitymatch> </entitymapping>
This describes three different "entitymappings". Every mapping is given an unique name, in this case "fern", "carrot" and "boulder". The name is of no importance for the rules and only used for easy lookup. Every mapping is comprised of three different types of elements: "matches", "cases" and "actions".
The "matches" tells the engine what to look for. There are a couple of different variants available.
- entitymatch: do a comparison against the type of the entity (for example an axe, a skeleton, a pig etc.). The first element under the "entitymapping" element must always be an entitymatch.
- attributematch: do a comparison against a certain attribute on the entity. The name of the attribute is specified through the "attribute" attribute (phew!). For example: <attributematch attribute="style" > would do a comparison against the "style" attribute. Note that currently we can only specify first level attributes. If the need arised we might add the ability to specify nested attributes in future versions. By default the system will assume that the attribute is of string type, but by adding a "type" attribute we can tell the system to handle it differently. Currently there's support for the types "numeric" and "function". By using the "numeric" type we tell the system to do all case comparisons against a numeric value. The "function" type tells the system to treat the attribute in a special way. The only supported attribute currently is "height". This is because the height is not set attribute but is calculated by subtracting the bottom of the bounding box from the top. When the "height" function is used all cases will be numerically compared, just as if we had used the "numeric" type. Examples of this:
<entitymapping name="oak"> <entitymatch> <case equals="oak"> <action type="display-model">oak</action> <attributematch type="function" attribute="height"> <case> <caseparam type="lesser">3</caseparam> <action type="display-model">oak_sapling</action> </case> <case> <caseparam type="greaterequals">3</caseparam> <caseparam type="lesserequals">6</caseparam> <action type="display-model">oak_young</action> </case> </attributematch> </case> </entitymatch> </entitymapping>
- outfitmatch: do a comparison against another entity which is outfitted on the entity. This is useful when you want to change the appearance of the model depending on what the entity is wearing. For example, a male entity might have a shirt outfitted. Depending on the colour of the shirt we want to use different kinds of materials. The attribute "attachment" specifies which attach point we should look at. In our shirt example, the attach point would perhaps be "body" or "torso". All nested matches that are below the outfit match will refer to the outfitted entity instead of the main entity. So for example, if you nest an attributematch which does a comparison against the attribute "colour" within an outfitmatch for the attachpoint of "torso" the system will look up the "colour" attribute of the entity which is attached to the "torso", not the colour attribute of the main entity.
For each "match" there's one or many different cases. These are iterated and evaluated in order by the engine for each match, and if any of them matches, all elements nested within the case will be processed. How the case will be evaluated depends on the <caseparam> elements. Each caseparam element has a "type" attribute and a contained value, for example
The type of case parameters available are dependent on the kind of match it belongs to.
- entitymatch: The only type available is "equals". The value contained is then the name of the entity type. When multiple caseparams are available, the system consider it a match if any one of them matches.
- attributematch: if no type is specified the system will do a string comparison and the only case param type available is the "equals" type. If "numeric " or "function" is used however we'll have access to the types "equals", "lesser", "lesserequals", "greater" and "greaterequals". If both a less and greater caseparam is present the system will do a range comparison, and only yield true if the value of the attribute is within the range.
- outfitmatch: This works very much like the entitymatch, and only "equals" is available. It will do a string comparison against the name of the outfitted entity type.
When a case evaluates to true, all contained actions will be executed. This is where we're able to alter the way the entity is presented. The type of action is specified by the "type" attribute. These actions are available:
- display-model: This tells Ember to show a certain Model. This is where we bind the Model and the Model Mapping system together. The contained value is the name of the model. For example:
- display-part: Tells Ember to show a certain part of the Model. This lets us turn on and off submeshes and use different materials. An example could be a humanoid mesh with a shirt submesh. By default we don't want to show the shirt, but if the entity in question has a child shirt entity attached to the torso we might want to show it. We thus create a Part in the Model definition for the shirt, names it "shirt" and set it to be hidden by default. In our entity mapping rule we then add the appropriate matches and cases which will end up in
In many cases we just want to connect a Model to an Entity type without any fancy extra rules. We then use the shorthand tag autoentitymapping which takes a entity type name and an optional model name. The most basic format is
<autoentitymapping name="boat" />
This will add a mapping named "boat" between the entity type "boat" and the Model "boat". Optionally a Model name can be used, like this:
<autoentitymapping name="boat" model="goatboat" />
This will add a mapping named "boat" between the entity type "boat" and the Model "goatboat".
Note that this is a shorthand and will be expanded in the entity mapping engine to proper matches, cases and actions. The first example here is thus equivalent to
<entitymapping name="boat"> <entitymatch> <case> <caseparam type="equals">boat</caseparam> <action type="display-model">boat</action> </case> </entitymatch> </entitymapping>
Nesting in mappings
The ability to nest matches and actions within cases allows for very advanced functionality. We'll use an oak as an example. Say we have two different oak meshes, "oak_large" and "oak_small". We want to use the smaller mesh when the oak is less or equal to 6 meters, and the larger one when the height is higher. In addition, we have two different materials, one for English oak and one for Canadian oak. For this we've created two different Models which in turn each have two parts, "english" and "canadian". The difference between the parts is that one is set to use the english oak material and the other is set to use the canadian oak material. The parts also both belong to the same group, which means that only one of them can be active at any time. If we enable the "english" part, the "canadian" part will automatically be disabled. So our goal here is to create a entity mapping which first uses the height of the entity to determine what model to use, and then uses the entity attribute "style" to determine whether to show the "english" part or the "canadian" part. We assume that the "style" attribute will contain either "englishoak" or "canadianoak".
<entitymapping name="oak"> <entitymatch> <case equals="oak"> <attributematch type="function" attribute="height"> <case> <caseparam type="lesserequals">6</caseparam> <action type="display-model">oak_small</action> <attributematch attribute="style" > <case equals="englishoak"> <action type="display-part">english</action> </case> <case equals="canadianoak"> <action type="display-part">canadian</action> </case> </attributematch> </case> <case> <caseparam type="greater">6</caseparam> <action type="display-model">oak_large</action> <attributematch attribute="style" > <case equals="englishoak"> <action type="display-part">english</action> </case> <case equals="canadianoak"> <action type="display-part">canadian</action> </case> </attributematch> </case> </attributematch> </case> </entitymatch> </entitymapping>
More advanced mapping examples can be found in the default Ember entitymapping file.