Table of Contents
- 5.1. Property Types
- 5.2. Server to Client bandwidth usage of Property updates
- 5.3. Default Values
- 5.4. Data Distribution
- 5.5. Implementing Custom Property Data Types
- 5.6. Volatile Properties
- 5.7. LOD (Level of Detail) on Properties
- 5.8. Bandwidth Optimisation: Send Latest Only
- 5.9. Bandwidth Optimisation: Is Reliable
- 5.10. Detailed Position
- 5.11. Appeal Radius
- 5.12. Temporary Properties
- 5.13. Persistent
- 5.14. User Data Object Linking With UDO_REF Properties
Properties describe what the state of an entity is. Like traditional object systems, a BigWorld property has a type and a name. Unlike traditional object systems, a property also has distribution properties that affect where and how frequently it is distributed around the system.
Properties are declared in the entity's definition file (named
),
in a section named <res>
/scripts/entity_defs/<entity>
.defProperties
.
The grammar for property definition is displayed below:
<root> ... <Properties> <propertyName
> <!-- type of this property --> <Type>TYPE_NAME
</Type><!-- Method of distribution --> <Flags>
DISTRIBUTION_FLAGS
</Flags><!-- Default value (optional) --> <Default>
DEFAULT_VALUE
</Default><!-- Is the property editable? (true/false) (optional) --> <Editable> [true|false] </Editable> <!-- Level of detail for this property (optional) --> <DetailLevel>
LOD
</DetailLevel><!-- Is the property persistent? --> <Persistent> [true|false] </Persistent>
<!-- Is the property indexed? --> <Indexed> [true|false]
<!-- Is the property index unique? --> <Unique> [true|false] </Unique> </Indexed> <!-- Is at most one change sent to clients in a packet? --> <SendLatestOnly> [true|false] </SendLatestOnly>
<!-- Is change sent reliably? --> <IsReliable> [true|false] </IsReliable>
</
propertyName
> </Properties> ... </root>
— Property definition syntax<res>
/scripts/entity_defs/<entity>
.def
For details, see Property Types. |
|
For details, see Data Distribution. |
|
For details, see Default Values. |
|
For details, see LOD (Level of Detail) on Properties. |
|
For details, see The Database Layer. |
|
For details, see Database Indexing. |
|
For details, see Bandwidth Optimisation: Send Latest Only. |
|
For details, see Bandwidth Optimisation: Is Reliable. |
BigWorld needs to efficiently transmit data over a network between its various components. For this purpose, BigWorld definition file describes the type of each property of an entity (despite the fact that BigWorld is scripted using Python — an untyped language).
Because bandwidth conservation is important in implementing an MMOG, property types should be selected such that they are the smallest type (in terms of number of bits) that can represent the data.
The following list summarises the primitive types available for BigWorld properties:
-
BLOB — Size (bytes):
N
+k
Binary data. Similar to a string, but can contain NULL characters.
Stored in base-64 encoding when in XML, e.g., in the XML database.
N
is the number of bytes in the blob, andk
=4. -
FLOAT32 — Size (bytes): 4
IEEE 32-bit floating-point number.
Note
The MySQL database may have less precision than this.
-
FLOAT64 — Size (bytes): 8
IEEE 64-bit floating-point number.
Note
The MySQL database may have less precision than this.
-
INT8 — Size (bytes): 1 — Range: From: -128 To: 127
Signed 8-bit integer.
-
INT16 — Size (bytes): 2 — Range: From: -32,768 To: 32,767
Signed 16-bit integer.
-
INT32 — Size (bytes): 4 — Range: From: -2,147,483,648 To: 2,147,483,647
Signed 32-bit integer.
-
INT64 — Size (bytes): 8 — Range: From: -9,223,372,036,854,775,808 To: 9,223,372,036,854,775,807
Signed 64-bit integer.
-
MAILBOX — Size (bytes): 12
A BigWorld mailbox.
Passing an entity to a MAILBOX argument automatically converts it to MAILBOX.
For details, see Mailboxes.
-
PYTHON — Size(bytes): Size of pickled string, as per STRING
Uses the Python pickler to pack any Python type into a string, and transmits the result.
This should not be used between client and server, as it is insecure and inefficient.
It is recommended to use a user data type for production code. For more details, see Implementing Custom Property Data Types.
-
STRING — Size (bytes):
N
+k
Character string (non-Unicode).
N
is the number of characters in the string, andk
=4. -
UINT8 — Size(bytes): 1 - Range: From: 0 To: 255
Unsigned 8-bit integer.
-
UINT16 — Size(bytes): 2 — Range: From: 0 To: 65,535
Unsigned 16-bit integer.
-
UINT32 — Size(bytes): 4 — Range: From: 0 To: 4,294,967,295
Unsigned 32-bit integer.
This type may use Python's long type instead of int, and so might be less efficient than INT32.
-
UINT64 — Size(bytes): 8 — Range: From: 0 To: 18,446,744,073,709,551,615
Unsigned 64-bit integer.
-
UNICODE_STRING — Size (bytes): Up to 4
N
+k
Character string (Unicode).
N
is the number of characters in the string, andk
=4. Streamed as UTF-8. -
VECTOR2 — Size(bytes): 8
Two-dimensional vector of 32-bit floats. Represented in Python as a tuple of two numbers (or Math.Vector2).
-
VECTOR3 — Size(bytes): 12
Three-dimensional vector of 32-bit floats. Represented in Python as a tuple of three numbers (or Math.Vector3).
-
VECTOR4 — Size(bytes): 16
Four-dimensional vector of 32-bit floats. Represented in Python as a tuple of four numbers (or Math.Vector4).
The following sections describe the composite types available in BigWorld.
BigWorld also has ARRAY and TUPLE types, which can create an array of values of any of the BigWorld primitive types.
Properties of ARRAY type have a byte size calculated by the formula below:
N * t + k
The components of the formula are described below:
-
N — Number of elements in the array.
-
t — Size of the type contained in the array.
-
k — Constant.
The BigWorld TUPLE type is represented in script by the Python tuple type, while the BigWorld ARRAY type is represented in script by Python list type.
Tuples are specified as follows:
<Type> TUPLE <of> [TYPE_NAME|TYPE_ALIAS] </of> [<size> n </size>] </Type>
— TUPLE declaration syntax<res>
/scripts/entity_defs/<entity>
.def
Arrays are specified as follows:
<Type> ARRAY <of> [TYPE_NAME|TYPE_ALIAS] </of> [<size> n </size>] </Type>
— ARRAY declaration syntax<res>
/scripts/entity_defs/<entity>
.def
In case the size of an ARRAY
or
TUPLE
is specified, then it must have the
declared n elements. Adding or deleting elements
to fixed-sized ARRAY
or
TUPLE
is not allowed. If the default value is not
specified, then a fixed-sized ARRAY
or
TUPLE
will contain n default
values of the element type.
Arrays have a special method called
equals_seq()
that can be used for performing
element-wise Boolean equality testing against any arbitrary Python
sequence (including Python lists and tuples). For example:
self.myList = [1,2,3] self.myList.equals_seq( [1,2,3] ) # should return True self.myList.equals_seq( (1,2,3) ) # should return True
Arrays efficiently propagate changes. This includes assigning to individual elements, appending, extending, removing, popping and slice assignment.
For example, each of the following are propagated efficiently.
self.myList = [1, 2, 3, 4, 5] self.myList[ 3 ] = 8 self.myList.append( 6 ) self.myList.extend( [7, 8] ) self.myList += [9, 10] self.myList.pop() self.myList.remove( 7 ) self.myList[ 2 : 5 ] = [11, 12] del self.myList[ 2 ] del self.myList[ 1 : 4 ]
Arrays can not only contain aliased data types, but may also be aliased themselves. For more details, see Alias of Data Types.
The FIXED_DICT data type allows you to define dictionary-like attributes with a fixed set of string keys. The keys and the types of the keyed values are predefined.
The declaration of a FIXED_DICT is illustrated below:
<Type> FIXED_DICT <Parent>ParentFixedDictTypeDeclaration
</Parent> <Properties> <field
> <Type>FieldTypeDeclaration
</Type> </field
> </Properties> <AllowNone> true|false </AllowNone></Type>
FIXED_DICT data type declaration
This data type may be declared anywhere a type declaration may
appear, e.g., in
[1], in
<res>
/scripts/entity_defs/alias.xml
,
as method call arguments, etc.
<res>
/scripts/entity_defs/<entity>
.def
The code excerpt below shows the declaration of a FIXED_DICT attribute:
<root> <TradeLog> FIXED_DICT <Properties> <dbIDA> <Type> INT64 </Type> </dbIDA> <itemsTypesA> <Type> ARRAY <of> ITEM </of> </Type> </itemsTypesA> <goldPiecesA> <Type> GOLDPIECES </Type> </goldPiecesA> </Properties> </TradeLog> </root>
fantasydemo/res/scripts/entity_defs/alias.xml
Instances of FIXED_DICT can be accessed and modified like a Python dictionary, with the following exceptions:
-
Keys cannot be added or deleted
-
The type of the value must match the declaration.
For example:
if entity.TradeLog[ "dbIDA" ] == 0: entity.TradeLog[ "dbIDA" ] = 100
Example of FIXED_DICT usage in script
Alternatively, it also supports the following:
if entity.TradeLog.dbIDA == 0: entity.TradeLog.dbIDA = 100
Example of FIXED_DICT usage as struct in script
Note
Using struct syntax can cause problems with name collisions with FIXED_DICT methods.
A FIXED_DICT instance can be set using a Python dictionary that has a superset of the keys required. Any unnecessary keys in the dictionary are ignored.
For example:
entity.TradeLog = { "dbIDA" : 100, "itemsTypesA" : [ 1, 2, 3 ], "goldPiecesA" : 1000, "redundantKey" : 12345 }
Example of FIXED_DICT instance being set using a Python dictionary
Changes to FIXED_DICT values are propagated
efficiently wherever a change to the whole property would be
propagated, i.e., to ghosts and to clients
— including ownClients
.
The default value of a FIXED_DICT data type can be specified at the entity property level. For example:
<root> <Properties> FIXED_DICT <someProperty> <Type> TradeLog </Type> <!-- From last example --> <Default> <dbIDA> 0 </dbIDA> <itemsTypesA> <item> 101 </item> <item> 102 </item> </itemsTypesA> <goldPiecesA> 100 </goldPiecesA> </Default> </someProperty> </Properties> </root>
Example of specifying default value of a FIXED_DICT data type in an entity definition file
If the <Default>
section is not
specified, then the default value of a FIXED_DICT
data type will depend on the value of the
<allowNone>
tag, as described below:
Table 5.1. Default values for a FIXED_DICT without a <Default> section.
<AllowNone> | FIXED_DICT default value |
---|---|
True | Python None object. |
False |
Python dictionary with keys as specified in the type definition. Each keyed value will have a default value according to its type. For example, a keyed value of INT type will have a default value of 0. |
There are two ways to incorporate user-defined Python classes into BigWorld entities: wrapping a FIXED_DICT data type, or implementing a USER_TYPE.
The FIXED_DICT data type supports being wrapped by a user-defined Python type. When a FIXED_DICT is wrapped, BigWorld will instantiate the user-defined Python type in place of a FIXED_DICT instance. This enables the user to customise the behaviour of a FIXED_DICT data type.
The type system can also be arbitrarily extended with the USER_TYPE type. Unlike a wrapped FIXED_DICT type, the structure of a USER_TYPE type is completely opaque to BigWorld. As such, the implementation of a USER_TYPE type is more involved. The implementation of the type operations is performed by a Python object (such as an instance of a class) written by the user. The Python object serves as a factory and serialiser for instances of that type, and it can choose to use whatever Python representation of that type it sees fit — it can be as simple as an integer, or it can be an instance of a Python class.
For more details on custom user types, see Implementing Custom Property Data Types.
BigWorld also allows aliases of types to be created. Aliases are a
concept similar to a C++ typedef, and are listed in
the XML file
.
The format is described below:
<res>
/scripts/entity_defs/alias.xml
<root> ... other alias definitions ... <ALIAS_NAME> TYPE_TO_ALIAS [<Default> Value </Default>] </ALIAS_NAME> </root>
— Data type alias declaration syntax<res>
/scripts/entity_defs/alias.xml
For details, see Default Values. |
Some examples of useful aliases are described in the list below:
Table 5.2. Entity Types
Alias | Maps to | Description |
---|---|---|
ANGLE | FLOAT32 | An angle measured in radians. |
BOOL | INT8 |
A Boolean type (encoded as zero=false, non-zero=true). Mapped to INT8, the smallest BigWorld type. |
INFO | UINT16 | Element of information about a mission. |
MISSION_STATS | ARRAY <of> INFO </of> |
Array of mission information data elements (i.e., INFO type alias). Note that this is an aliased array, and the type of its elements is an aliased type. |
OBJECT_ID | INT32 | Handle to another entity. The name makes clear the property contains a handle to an entity. |
STATS_MATRIX | ARRAY <of> MISSION_STATS </of> |
Matrix of mission information data elements (i.e., INFO type alias). Note that this is an aliased array, and the type of its elements is another aliased array. |
Using the syntax for alias definition to the aliases describe above, we have the following file:
<root> <!-- Aliased data types --> <OBJECT_ID> INT32 </OBJECT_ID> <BOOL> INT8 </BOOL> <ANGLE> FLOAT32 </ANGLE> <INFO> UINT16 </INFO> <!-- Aliased arrays ? <MISSION_STATS> ARRAY <of> INFO </of> </MISSION_STATS> <STATS_MATRIX> ARRAY <of> MISSION_STATS </of> </STATS_MATRIX> </root>
— Definition of data type alias<res>
/scripts/entity_defs/alias.xml
With aliases, one can also define custom Python data types, which
have their own streaming semantics on the network. We declare these
types in the file
file as follows:
<res>
/scripts/entity_defs/alias.xml
<root> <ALIAS_NAME> USER_TYPE <implementedBy> UserDataType.instance </implementedBy> </ALIAS_NAME> </root>
<res>
/scripts/entity_defs/alias.xml
— Custom Python data type declaration syntax
For more details on this mechanism, see Implementing Custom Property Data Types.
When the server sends a property update to the client, it generally includes a byte representing the length of the data being streamed. However, if the server is able to determine that the size of a particular property is always the same when streamed to the client, that byte can be eliminated.
The server will consider a property to be a fixed-size type if it is any combination of:
-
A Primitive Type with a constant size. See Primitive Types.
-
An ARRAY or TUPLE with a declared size and containing a fixed-size type. See ARRAY and TUPLE Types.
-
A FIXED_DICT which cannot be None, contains only fixed-size types, and which is not wrapped with a class implementing
addToStream
. See FIXED_DICT Data Type and Implementing Custom Property Data Types.
Slice assignment of ARRAY elements and updates to individual ARRAY or TUPLE elements or FIXED_DICT values do not benefit from this optimisation for fixed-size types.
You can optimise your server to client bandwidth usage by ensuring that values which are updated together are fixed-size, and bundled into FIXED_DICT structures. If the entire FIXED_DICT is updated at once, then only a single message needs to be sent, without a byte indicating the length of the message.
Properties that are generally updated individually are better off not being bundled into FIXED_DICTs, as updating an individual element of a FIXED_DICT uses more bandwidth than updating a top-level property.
Where possible, avoid propagating variable-sized properties to the client. The cost of a variable-sized property is only one byte though, so for example an ARRAY should have its size specified only if it is always that long, rather than to indicate the maximum interesting length.
One other thing to note is that there is a limited number of identifiers for properties to propagate to the client, and if you have more properties for a given entity type than this, the excess properties will be sent slightly less efficiently (generally one byte more per value update) and as if they were variably-sized. The server prefers to send large fixed-size or variably-sized properties using this 'overflow' mechanism.
The dumpEntityDescription DBMgr Configuration Option can be used to examine the client to server bandwidth requirements of your entity properties and methods. See the document Server Operations Guide's section DBMgr Configuration Options.
When an entity is created, its properties are initialised to their
default values. Default values can be overridden at the property level (in
the entity definition file[2]) or at the type level (in
alias.xml
[3]).
The default value for each type and the syntax for overriding it are described below:
-
ARRAY — Default: []
Example:
<Default>
<item> Health potion </item> <item> Bear skin </item> <item> Wooden shield </item> </Default>
-
BLOB — Default: ''
Example:
<Default> SGVsbG8gV29ybGQhB </Default>
<!-Hello World! -->
-
FIXED_DICT
For details, see FIXED_DICT Data Type.
-
FLOAT32 — Default: 0.0
Example:
<Default> 1.234 </Default>
Note
The MySQL database may have less precision than specified here. If so, this value should be modified to match the precision of the database.
-
FLOAT64 — Default: 0.0
Example:
<Default> 1.23456789 </Default>
Note
The MySQL database may have less precision than specified here. If so, this value should be modified to match the precision of the database.
-
INT8, INT16, INT32, INT64 — Default: 0
Example:
<Default> 99 </Default>
-
MAILBOX — Default: None
Default value cannot be overridden.
-
PYTHON — Default: None
Example:
<Default> { "Strength": 90, "Agility": 77 } </Default>
-
STRING — Default: ''
Example:
<Default> Hello World! </Default>
-
TUPLE — Default: ()
Example: See ARRAY data type
-
UINT8, UINT16, UINT32, UINT64 — Default: 0
Example:
<Default> 99 </Default>
-
UNICODE_STRING — Default: u''
Example:
<Default> Hello World! (this is a UTF-8 string) </Default>
Value must be specified without quotes, and must be encoded as UTF-8[4].
-
USER_TYPE — Default: Return value of the user-defined defaultValue() function.
Example:
<Default> <intVal> 100 </intVal> <strVal> opposites </stringVal> <dictVal> <value> <key> good </key> <value> bad </value> </value> </dictValue> </Default>
-
VECTOR2 — Default: PyVector of 0.0 of the appropriate length.
Example:
<Default> 3.142 2.71 </Default>
-
VECTOR3 — Default: PyVector of 0.0 of the appropriate length.
Example:
<Default> 3.142 2.71 1.4 </Default>
-
VECTOR4 — Default: PyVector of 0.0 of the appropriate length.
Example:
<Default> 3.142 2.71 1.4 3.8 </Default>
Properties represent the state of an entity. Some states are only relevant to the cell, others only to the base, and yet others only to the client. Some states, however, are relevant to more than one of these.
Each property then has a distribution type that specifies to BigWorld which execution context (cell, base, or client) is responsible for updating the property, and where to propagate its value within the system.
Data distribution is set up by specifying the sub-section
<Flags>
of the section
<Properties>
in the file
.
<res>
/scripts/entity_defs/<entity>
.def
The bit flags available are defined in
bigworld/src/lib/entitydef/data_description.hpp
, and
are described in the list below:
-
DATA_BASE
Required flags: N/A — Excluded flags: DATA_GHOSTED — Master value on: Base
Data will be updated on the base, and will not be available on the cell.
-
DATA_GHOSTED
Required flags: N/A — Excluded flags: DATA_BASE — Master value on: Cell
Data will be updated on the cell, and will be ghosted on other cells.
This means that it is safe to read the value of this property from another entity, because BigWorld safely makes it available even across cell boundaries.
-
DATA_OTHER_CLIENT
Required flags: DATA_GHOSTED — Excluded flags: N/A — Master value on: Cell
Data will be updated on the cell, and made available to clients who have this entity in their AoI.
This makes the property safe to read from the client for any entity, except for that client's player avatar entity. This flag is often combined with DATA_OWN_CLIENT to create a property that is distributed to all clients.
-
DATA_OWN_CLIENT
Required flags: N/A — Excluded flags: N/A — Master value on: Base, if DATA_BASE is set. Otherwise, on cell.
Data is propagated to client owning this entity.
This only makes sense with player entities.
The list below describes the valid combinations of the above bit flags:
-
ALL_CLIENTSA
Available to: Other cells, Cell, Own client, Other clients
Property is available to all entities on cell and client.
Corresponds to setting both OWN_CLIENT and OTHER_CLIENTS flags.
Examples include:
-
The name of a player.
-
The health status of a player or a creature.
-
-
BASE
Available to: Base
Property is only available on the base.
Examples include:
-
List of members of a chat room.
-
Items in a character's inventory.
-
-
BASE_AND_CLIENT
Available to: Base, Own client
Property is available on the base and on the owning client. Corresponds to setting both OWN_CLIENT and BASE flags.
Note
Properties of this type are only synchronised when the client entity is created. Neither the client nor the base is automatically updated when property changes. Methods must be used to propagate new value, which is simple, since only one player needs to receive it.
-
CELL_PRIVATE
Available to: Cell
Property is only available to its entity, and only on cell.
Examples include:
-
Properties of an NPCs 'thoughts' in AI algorithms.
-
Player properties relevant to game play, but dangerous to allow players to see (e.g., healing time after battle).
-
-
CELL_PUBLIC
Available to: Other cells, Cell
Property is available only on the cell, and is available to other entities.
Examples include:
-
The mana level of a player (which can be seen only by enemies, not by other players).
-
The call sign for grouping from enemy NPC.
-
-
CELL_PUBLIC_AND_OWNA
Available to: Other cells, Cell, Own client
Property is available to other entities on the cell, and to this one on both the cell and the client.
Unlike OWN_CLIENT, this data is also ghosted, and therefore available to other entities on the cell.
-
EDITOR_ONLY
Available to: World Editor
This value may be useful when using
BigWorld
.fetchEntitiesFromChunks
from a BaseApp. It could be used to decide programmatically whether a particular entity should be loaded.For example, you may associate a level of difficulty with each entity, so entity will only be loaded if the mission's level of difficulty is high enough.
-
OTHER_CLIENTSA
Available to: Other cells, Cell, Other clients
Property is available from client to entities that are not this player's avatar. Also available on cell to other entities.
Examples include:
-
The state of dynamic world items (e.g., doors, loot containers, and buttons).
-
The type of a particle system effect.
-
The player who is currently sitting on a seat.
-
-
OWN_CLIENTA
Available to: Cell, Own client
Property is only available to this entity, on both the cell and the client.
Examples include:
-
The character class of a player.
-
Number of experience points for a player.
-
A — When properties with this distribution flag are updated by server, an implicit method is called on client. For details, see Client callbacks on property changes.
When choosing a distribution flag for a property, consider the points described below:
-
Which methods need the property?
You have to make the property available on an execution context (cell, base, or client) if that context has a method that manipulates the property.
-
Does this property need to be accessed by other entities?
This could include methods being called to access its value. If this is the case, we need to make the property ghosted.
When doing this, remember that the ghosted entities' properties may be a little 'lagged', i.e., they may not represent the exact state of an entity at a given time. Also, remember that other entities can only read the property; only the entity that owns the property may change it.
-
Is the client interested in this value directly?
Client/server bandwidth is scarce, so the number of properties on the client needs to be minimised.
Sometimes, a group of properties can be maintained on the cell and only a derived additional property needs to be sent to the client. For example, a client part would probably not need to know that a combination of six AI state variables are causing a guard to be angry; they would however need to know the derived value that the guard is brandishing an axe.
-
Could a player cheat by seeing this property?
If so, then care must be taken about sending it to the client.
-
There can only be one master value of any property.
The master value must reside on either the base or cell. Consequently, if the same property is available on both the base and the cell, the other holder of the property needs to have the value propagated to it via a method.
Data propagation occurs when the entity is first created. Subsequent modifications to properties will only be local to the component, except when the modification occurs in a CellApp, in which case the change will be automatically propagated to all interested parties. For example, CELL_PUBLIC properties are propagated to all other CellApps that have a ghost of the entity, OTHER_CLIENTS properties are propagated to all clients that have the entity in their AoI, and so on.
When changing the value of a property in a component other than a CellApp, the change can be manually propagated using remote method calls. For details, see Methods.
When modifying a property that will be propagated to an entity's ghost on an adjacent cell, that is for CELL_PUBLIC, OTHER_CLIENTS and ALL_CLIENTS properties, optional callbacks can be implemented on the cell entity class to react to those property updates for those ghosted cell entities. They are similar to the client-side callbacks:
-
@bwdecorators.callableOnGhost
def set_
<property name>
( self, oldValue )This method is called when the property has changed. The
oldValue
parameter is the old value of the property, the new value has already been set.If the change is a nested and the
setNested_<
is implemented, this method will not be called. Similarly, if the change is a slice change andproperty name
>setSlice_<
is implemented, this method will not be called.property name
> -
@bwdecorators.callableOnGhost
def setNested_
<property name>
( self, changePath, oldValue )This method is called for nested property changes, for example when a change occurs for an ARRAY element or a FIXED_DICT sub-property. If this method does not exist, the
set_<
callback will be called instead, if it exists.property name
>The
changePath
parameter contains the path to the change, for ARRAY values, the index in the array is used, for FIXED_DICT, the string of the key is used as a path component.For example, suppose we have the following property definition:
<myProperty> <Type> ARRAY <of> FIXED_DICT <Properties> <a> <Type> ARRAY <of> INT32 </Type> <a/> <b> <Type> STRING </Type> </b> </Properties> </of> </Type> <Flags> CELL_PUBLIC </Flags> </myProperty>
Suppose following statement on the cell entity is run:
>>> entity.myProperty[3].a[5] = 8
This would result in a call to
setNested_myProperty
, withchangePath
set to:[3, 'a', 5]
-
@bwdecorators.callableOnGhost
def setSlice_
<property name>
( self, changePath, oldValue )This method is called for slice changes to ARRAY properties. If this method does not exist, the
set_<
callback will be called instead, if it exists.property name
>Examples of slice changes are below:
>>> entity.myProperty.append[3].a.append( 10 ) >>> entity.myProperty[3:9] = [50, 6, 9, 10]
The changePath parameter contains the path to the change. Refer the documentation for
setNested_
above. The new slice is described by start and end indices that are the last element in the<property name>
changePath
list.
Note that these callbacks are never called on the real cell entity. As they are callable on ghosted cell entities, they must be decorated with the callableOnGhost decorator function from the bwdecorators module.
Changes to properties of PYTHON and custom user types are not automatically propagated, unless the property is reassigned.
This behaviour mainly affects composite Python types like dictionaries, arrays, and classes, because modifications to the object do not cause data propagation unless the property is reassigned to itself.
For example, if entity e has the property as illustrated below:
<pythonProp> <Type> PYTHON </Type> ... </pythonProp>
Assigning a new value to pythonProp
will
cause data propagation:
e.pythonProp = { 'gold': 100 }
However, modifying the value will not cause data propagation:
e.pythonProp[ 'gold' ] = 50 e.pythonProp[ 'arrows' ] = 200
Different parts of the entity will see different values for
pythonProp
, unless data propagation is manually
triggered by reassigning the property back to itself:
e.pythonProp = e.pythonProp
Custom data types are useful for the implementation of data structures with complex behaviour that is shared between different components, or that must be attached to cell entities (in which case they must be able to be transferred from one cell to another).
By default, the FIXED_DICT data type behaves like a Python dictionary. This behaviour can be changed by replacing the dictionary-like FIXED_DICT type with another Python type (referred to as a wrapper type in this document).
To do so, specify a type converter object in the
<implementedBy>
section in the
FIXED_DICT type declaration. For example:
<Type> FIXED_DICT <implementedBy> CustomTypeConverterInstance </implementedBy> <Properties> ... </Properties> ... </Type>
Declaration of a Wrapped FIXED_DICT Data Type
CustomTypeConverterInstance
must be a
Python object that converts between FIXED_DICT
instances and wrapper instances.
It must implement the following methods:
Table 5.3. Methods that should be implemented by wrapper type.
Method | Description |
---|---|
addToStream (
self , obj
)
|
Optional method that converts a wrapper instance to a string suitable for transmitting over the network. The If this method is present, then
If this method is not present, then wrapper
instances are transmitted over the network by first converting
them to FIXED_DICT instances using the
|
createFromStream (
self , stream
)
|
Optional method that creates an instance of the wrapper type from its string network form. The If this method is present, then
|
createObjFromDict (
self , dict
)
|
Method to convert a FIXED_DICT instance to a wrapper instance. The |
getDictFromObj (
self , obj
)
|
Method to convert a wrapper instance to a FIXED_DICT instance. The
|
isSameType (
self , obj
)
|
Method to check whether an object is of the wrapper type. The |
It is often desirable to wrap a FIXED_DICT data type with a class to facilitate object-oriented programming.
import cPickle class MyCustomType( object ): # wrapper type def __init__( self, dict ): self.a = dict[ "a" ] self.b = dict[ "b" ] ... # other MyCustomType methods class MyCustomTypeConverter( object ): # type converter class def getDictFromObj( self, obj ): return { "a": obj.a, "b": obj.b } def createObjFromDict( self, dict ): return MyCustomType( dict ) def isSameType( self, obj ): return isinstance( obj, MyCustomType ) def addToStream( self, obj ): # optional return cPickle.dumps( obj ) def createFromStream( self, stream ): # optional return cPickle.loads( stream ) instance = MyCustomTypeConverter() # type converter object
— Wrapper type and type converter object<res>
/scripts/common/MyCustomTypeImpl.py
<Type> FIXED_DICT <implementedBy> MyCustomTypeImpl.instance </implementedBy> <Properties> <a> ... </a> <b> ... </b> </Properties> ... </Type>
Excerpt of a wrapped FIXED_DICT type declaration
The above example makes a FIXED_DICT type
behave as a class with members a
and
b
, instead of as a dictionary with the same
keys.
The drawback with the above example is that member updates are
not automatically propagated to other components. For example, if the
above data type is used in an entity attribute called
custType
, the following script code would only set
the value of the attribute for the local copy of the entity:
e.custType.a = 100 e.custType.b = 200
To ensure that all copies of the entity e have the updated
values, the attribute must be set to a different instance of
MyCustomType
with the updated values:
e.custType = MyCustomType( { "a": 100, "b": 200 } )
Alternatively, MyCustomType can be implemented using descriptors that reference the original FIXED_DICT instance:
class MemberProxy( object ): # descriptor class def __init__( self, memberName ): self.memberName = memberName def __get__( self, instance, owner ): return instance.fixedDict[ self.memberName ] def __set__( self, instance, value ): instance.fixedDict[ self.memberName ] = value def __delete__( self, instance ): raise NotImplementedError( self.memberName ) class MyCustomType( object ): # wrapper class a = MemberProxy( "a" ) b = MemberProxy( "b" ) def __init__( self, dict ): self.fixedDict = dict ... # other MyCustomType methods class MyCustomTypeConverter( object ): # type converter class def getDictFromObj( self, obj ): return obj.fixedDict # must return original instance def createObjFromDict( self, dict ): return MyCustomType( dict ) def isSameType( self, obj ): return isinstance( obj, MyCustomType ) # addToStream and createFromStream cannot be implemented
— Wrapper type and type converter object using
descriptors<res>
/scripts/common/MyCustomTypeImpl.py
In the above example, MyCustomType
references the original FIXED_DICT instance in its
fixedDict
member. Access to members
a
or b
will be redirected via
the descriptor class to the fixedDict
member. As
updates to FIXED_DICT instances are automatically
propagated to other components, updates to members
a
and b
are also automatically
propagated.
The drawback with this approach is that custom streaming is not possible. If the addToStream and createFromStream methods are implemented, then the custom object is created directly from the stream. Since it is not possible to instantiate a FIXED_DICT object in Python script, it will not be possible for the custom object to reference a FIXED_DICT object that will propagate partial changes.
The USER_TYPE data type predates the
FIXED_DICT data type, and much of its functionality
can be achieved by wrapping a FIXED_DICT data type.
However, USER_TYPE data type additionally allows
customising its representation as a
<DataSection>
.
A USER_TYPE data type consists of the following pieces:
-
A declaration of the Python instance implementing the USER_TYPE data type. For example:
<Type> USER_TYPE <implementedBy> UserType.instance </implementedBy> </Type>
— User type declaration syntax<res>
/scripts/entity_defs/<entity>
.defHowever, it is recommended to declare a USER_TYPE data type in
[5] to give it a name that we can use in the entity definition files [6] (named<res>
/scripts/entity_defs/alias.xml
).<res>
/scripts/entity_defs/<entity>
.def -
A class that defines methods to read and write this data type from various places.
-
A module, containing the above class, and an instance of this class, which will be used to serialise and unserialise the custom data type.
The custom data type might also declare a Python class that represents the type at runtime. A Python list, a dictionary, or some other native Python data type might also represent it.
The class we implement provides methods to serialise whatever Python type we use to represent a concept. This means that we can transmit the class over the network and serialise it to a database, simply by writing the appropriate methods in this class.
These methods are described in the list below:
Table 5.4. Custom data type serialisation methods.
Method | Description |
---|---|
addToStream (
self ,
obj )
|
Converts the Python object obj
into a string representation to be placed onto the network,
and return that string. It does the opposite of
For example, if your
type contains a single INT32 member, then
def addToStream( self, obj ): return struct.pack( "i", obj ) |
createFromStream (
self , stream
)
|
Creates a Python object from the
string passed in through The length
of the For example, if your type
contains a single INT32 member, then
def createFromStream( self, stream ): if len(stream) != 4: # one integer raise "Error: string has wrong length" else: return struct.unpack( "I", stream ) |
addToSection (
self , obj ,
section )
|
Adds a representation of
It is used for persisting properties into the database. Hence, if a property is not persistent, this method does not have to be implemented. |
createFromSection (
self , section
)
|
Creates and returns a Python
object from its persisted representation in section
It is
used for persisting properties into the database, and
parsing default values from
You should always implement this method,
even if you do not implement
|
fromStreamToSection (
self , stream ,
section )
|
Converts data from a
def fromStreamToSection( self, stream, section ): o = self.createFromStream( stream ) self.addToSection( o, section ) It can also be
implemented more efficiently (for instance if the
section.asBlob = stream |
fromSectionToStream (
self , section
)
|
Converts data from a
It can be implemented as follows: def fromSectionToStream( self, section ): o = self.createFromSection( section ) return self.addToStream( o ) It can also be
implemented more efficiently (for instance if the
return section.asBlob |
defaultValue (
self )
|
Returns a reasonable default value for this data type. It is used when there is no default value specified when this data type is used in a property. |
We place a class implementing these methods into a module in the
directory
,
and create an instance variable as an instance of this class.
<res>
/scripts/common
For example, we may define a module called
MyCustDataType.py
, as illustrated below:
class MyCustDataType: def addToStream( self, obj ): ... def createFromStream( self, stream ): ... def addToSection( self, obj, section ): ... def createFromSection( self, section ): ... def fromStreamToSection( self, stream, section ): ... def fromSectionToStream( self, section ): ... def defaultValue( self ): ... instance = MyCustDataType()
— Serialisation methods<res>
/scripts/common/MyCustDataType.py
If the property is persistent, and stored in a MySQL database, then an additional method has to be implemented. This method will declare the binding of the data into the database. For more details, see The Database Layer.
The variable instance is the object that performs the
manipulation of this data type by BigWorld. In the aliases file
,
we would include the following definition:
<res>
/defs/alias.xml
<root> ... <MY_CUSTOM_DATA_TYPE> USER_TYPE <implementedBy> MyCustDataType.instance </implementedBy> </MY_CUSTOM_DATA_TYPE>
— Definition of
MY_CUST_DATA_TYPE<res>
/defs/alias.xml
Some properties are updated more often than others, and almost all entities have a set of properties that need to be handled specially due to this. These properties are called volatile properties, and are pre-defined by the BigWorld engine.
Typically, properties flagged with OTHER_CLIENTS (or ALL_CLIENTS) are only sent to the appropriate client applications when the property changes. These changes are sent reliably. Properties that are deemed to be volatile are sent to the client each time that entity is considered by an AoI (via its priority queue mechanism). These are sent unreliably as a newer value will be sent the next time that entity is considered. For more details on how the AoI priority queue works, see Player AoI Updates.
The default volatile properties defined by BigWorld are outlined below:
Table 5.5. BigWorld's pre-defined volatile properties.
Property | Description |
---|---|
position |
The (x,y,z) position of the entity. Represented in Python as a TUPLE of three floats. |
|
Three extra volatile properties, which are typically used for the direction an entity is facing, but may be used for other purposes. They still must, however, have the ranges of the corresponding element of a direction: (-pi,pi) for
(-pi/2,pi/2) for
(-pi,pi) for
|
These properties are updated with an optimised protocol used between the client and the server, in order to minimise bandwidth.
The volatile properties are listed separately to the normal
properties in the file
.
<res>
/scripts/entity_defs/<entity>
.def
Each entity can decide which of these volatile properties are automatically updated. Additionally, they can have a priority attached to them. This priority determines a distance from the entity above which the property is no longer sent.
The syntax is as follows:
<root> ... <Volatile> <position/> | <position>float
</position> <yaw/> | <yaw>float
</yaw> <pitch/> | <pitch>float
</pitch> <roll/> | <roll>float
</roll> </Volatile> ...
— Declaration of volatile properties<res>
/scripts/entity_defs/<entity>
.def
This is how the volatility status and priority of a property are interpreted:
-
If a volatility status is not specified, then it will never be updated (
BigWorld
.VOLATILE_NEVER
). -
If a volatility status is specified:
-
If a priority is not specified, then property will always be updated, regardless of distance from entity (
BigWorld
.VOLATILE_ALWAYS
). -
If a priority is specified, then the value is used as the maximum distance from entity (in metres) for which property will still be updated.
-
Note
The volatile distance for pitch cannot be less than that of yaw and the volatile distance for roll cannot be less than that of pitch.
Supposing an entity the volatile properties as defined below:
<root> ... <Volatile> <position/> <yaw> 30.0 </yaw> <pitch> 25.0 </pitch> </Volatile> ... </root>
— Example definition<res>
/scripts/entity_defs/<entity>
.def
For the above example, we have the following for each property:
-
position
— Always updated (BigWorld
.VOLATILE_ALWAYS
) -
yaw
— Updated up to a distance of 30.0 metres. -
pitch
— Updated up to a distance of 25.0 metres. -
roll
— Never updated (BigWorld
.VOLATILE_NEVER
)
Note
Only non-moving entities should be defined without volatile properties.
Each position or direction change of an entity without any volatile properties is sent to the necessary clients in a detailed but less efficient way. This allows an entity's position to be correct when it is occasionally moved (e.g., a chair has been slightly moved). If this happen consistently, it can consume a lot of server to client bandwidth.
Sometimes bandwidth usage can be optimised by not distributing
information to clients that are distant. We can do this by attaching a
<DetailLevel>
tag to a property. This tag
determines the distance after which property changes will not be sent to
the client.
Note that this is purely an optimisation for the property. This option should only be used if bandwidth usage is proven to be too high. If this feature is enabled for the property, then you must test it very carefully to check if the result achieved in terms of game play is what you expected.
The definition of the LOD (level of detail) of a property in the
file
is described below:
<res>
/scripts/entity_defs/<entity>
.def
<root> ... <Properties> ... <modelNumber> ... <DetailLevel> NEAR </DetailLevel> </modelNumber> ...
— Declaration of LOD for property<res>
/scripts/entity_defs/<entity>
.def
The example above declared a LOD labelled NEAR
for the property. The actual value of NEAR is defined
in the sub-section <level>
of the section
<LodLevels>
in the entity's file.
For example, to subdivide the AoI into the ranges labelled NEAR, MEDIUM, and FAR (with everything further than FAR being transmitted whenever entities are within each other's AoI), the entity's definition file will include the lines below:
<root> ... <LODLevels> <level> 20 <label> NEAR </label> </level> <level> 100 <label> MEDIUM </label> </level> <level> 250 <label> FAR </label> </level> </LODLevels> ... </root>
— Definition of labels for LODs<res>
/scripts/entity_defs/<entity>
.def
The LODs specified for the entity in the example file above are illustrated below:

Location of LOD boundaries relative to the entity
Detail levels are inherited from parent definition files. Any level with the same label as a parent will modify that level, and any new levels will be added.
There is currently a limit of six levels of detail for each entity type
In addition to its parameter <label>
,
the sub-section <level>
can also have
<hyst>
parameter.
It is defined as illustrated in the example below:
<root> ... <LODLevels> <level> 20 <label> NEAR </label> <hyst> 4 </hyst> </level> <level> 100 <label> MEDIUM </label> <hyst> 10 </hyst> </level> <level> 250 <label> FAR </label> <hyst> 20 </hyst> </level> </LODLevels> ...
— Definition of hysteresis regions<res>
/scripts/entity_defs/<entity>
.def
This parameter defines a hysteresis region starting from the LOD's
outer boundary and moving outwards. It prevents frequent changes in the
LOD of a property, which saves significant processing time on the cell,
as properties do not have to change their priorities often. In order to
do this, the <hyst>
specifies a buffer region
around the boundary of a LOD level, which an entity must pass through
completely before changing to a lower LOD.
The declaration of the <hyst>
parameter
is optional, and if not declared, it will default to 10 metres.
As an example, consider a stationary entity, and another entity travelling through points A, B, C, D, E, and finally back to A, as illustrated in the diagram below:

Entity moving around LODs of another entity
We consider the minimum LOD of properties that will be propagated from the moving entity to the stationary entity, as listed in the table below:
Table 5.6. Entity moving around LODs of another entity.
Point | LOD | Reason |
---|---|---|
A | NEAR | Unaffected by hysteresis. |
B | NEAR | Entity has moved from NEAR to MEDIUM, but not yet completely through the hysteresis. |
C | MEDIUM | Entity has moved from NEAR to MEDIUM, and completely through the hysteresis. |
D | MEDIUM | Entity is still in MEDIUM. |
E | MEDIUM | Entity is still in MEDIUM. |
A | NEAR | Entity has moved from MEDIUM to NEAR. |
In the example above, we have the following regarding the change of LOD for the moving entity:
-
The change of LOD for the moving entity from NEAR to MEDIUM occurs at a distance of 24 metres from the stationary entity (20 metres as defined for the NEAR LOD, plus 4 metres for its hysteresis). If no
<hyst>
parameter were specified, the change would happen at 30 metres (since hysteresis would then default to 10 metres). -
The change of LOD for the moving entity from MEDIUM to NEAR occurs at 20 metres from the stationary entity (since hysteresis does not affect moving to a higher LOD).
When an OTHER_CLIENTS property of an entity changes, an event object is created and added to the event history. This event history is used when updating client applications that have this entity in their AoI. When this entity is considered, any events that have been added since the last time this entity was considered are sent to the client. There is the potential for multiple changes to a single property to be sent in a single update. For more details on how the AoI priority queue works, see Player AoI Updates.
If the SendLatestOnly flag is set on a property, only the latest change is kept in the event history. This avoids sending multiple changes. This can save bandwidth being sent to the client and can save some memory on CellApps if a property is changed frequently.
This value is false by default.
Client methods also have this flag. See Bandwidth Optimisation: Send Latest Only.
Note that changing the contents of ARRAY and FIXED_DICT data type instances should be avoided if the property is SendLatestOnly since this requires the entire property to be resent.
When an OTHER_CLIENTS property of an entity changes, a message is sent to appropriate client applications to update their view of this entity. By default, this message is sent reliably so that the change will be received even when there is packet loss. There may be rare situations where sending these unreliably is preferred. This is typically only used with the SendLatestOnly option and with a property that is changed continuously.
Setting SendLatestOnly to true and IsReliable to false and changing a value every game tick causes behaviour similar to volatile position and direction values.
Client methods also have this flag. See Bandwidth Optimisation: Is Reliable.
Note that changing the contents of ARRAY and FIXED_DICT data type instances should be avoided if the property has IsReliable set to false since this requires the entire property to be resent.
Entity position updates sent to the client are usually relative to the client position. These values are limited in magnitude to maxAoIRadius, which allows them to be compressed to conserve bandwidth.
However, if an entity does not have any volatile properties, it will implicitly send its detailed position to the clients interested in it. The detailed position is made up of absolute coordinates on the space. Since the position is not classified as volatile, it will be sent to all clients interested in the entity every time its value changes.
For entity types with volatile properties, a detailed position can be sent to interested clients by using the DetailedPosition option. This option is useful if higher precision is required for an entity's position updates than is provided by the usual, compressed values. However, it should be used sparingly, as it uses much larger types, and thus uses more bandwidth.
The DetailedPosition option can have a SendLatestOnly flag, which defaults to false if not specified. See Bandwidth Optimisation: Send Latest Only for more details. Using this flag will help reduce bandwidth usage for entity types whose positions change frequently.
The grammar for the DetailedPosition option is displayed below:
<root>
...
<DetailedPosition>
<SendLatestOnly> value
</SendLatestOnly>
</DetailedPosition>
...
</root>
It is sometimes desirable to have properties sent to the client from entities outside the client's AoI. For example, it could be that a large dragon should be visible from many kilometres away. Increasing the AoI radius to a significantly larger value is far from ideal, since it would drastically increase bandwidth usage due to unnecessary updates from many more entities.
A non-zero AppealRadius value specifies an area around the entity. If this area intersects with the client's AoI, property updates will be sent to the client as if the entity were in its AoI. For example, if the client's aoiRadius is 500m, and an entity's AppealRadius is set to 1500m, then the player will be able to see the entity from up to 2000m away. Specifically, the client will receive property updates from the entity if the distance between the avatar and the entity is less than the sum of the aoiRadius and the entity's AppealRadius, on both the X and Z axes.
In the diagram below, three clients, A, B and C, are near a large entity. The entity is within the AoI of Client C only. Client B, however, is close enough to the entity that its AoI intersects with the entity's area of appeal. For this reason, Client B will receive property updates as if the entity were in its AoI.

Clients approaching an entity's area of appeal
Entity types that set an AppealRadius will implicitly set the DetailedPosition/SendLatestOnly option. The relative position updates usually sent for an entity are restricted to values in the client's AoI, making entities outside this area too far away to have their positions represented. DetailedPosition is used because it allows values outside this range. See Detailed Position for more details.
Temporary properties can be used for properties that do not need to be backed up or offloaded with an entity.
The grammar for temporary property definition is displayed below:
<root> ... <TempProperties> <tempPropertyName1/
> <tempPropertyName2/
> ... </TempProperties> ... </root>
These should generally be rare but are useful for properties that cannot be streamed such as sockets or properties that are recreated on restoring. These apply to both cell and base entities.
Typically, there is at least one database table associated with each entity type in the game's entity database. This is where entities of this type can be persisted. Frequently, there are entity types that never need to be persisted.
You can avoid having a table created in the database by setting its Persistent property to false.
<root> ... <Persistent>false</Persistent> ... </root>
This defaults to true. The only real advantage in setting this to false is to reduce the number of tables created. There is no real performance impact.
There is a special property type, UDO_REF, that can be used in both entities and user data objects. This property type makes it possible to create a connection between an entity and a user data object, or between two user data objects. This property type is a key feature of user data objects, as it allows the creation of complex graphs made up of different types of user data objects and entities that can be used by the entity scripts as desired. A UDO_REF property is nothing more than a reference to a user data object. When an entity or a user data object with a UDO_REF property is loaded, the user data object referenced by the UDO_REF property could exist in an unloaded state if the user data object referenced hasn't been loaded yet. In this case, the script will only be able to get the user data object's unique identification number through the guid attribute. Once the referenced user data object is loaded, all it's attributes and properties can be accessed.
The most important example of this property type is in the PatrolNode user data object. The old patrol path system, including the old PATROL_PATH property type, have been deprecated. Patrol functionality is now achieved with the PatrolNode user data object, which can be linked to other PatrolNode objects through an array of UDO_REF properties. Entities that wish to patrol through a graph of PatrolNode objects just need have a UDO_REF property that links to a PatrolNode.
[1] For details on this file's grammar, see the document File Grammar Guide's section alias.xml
[2] For details, see the introduction to this chapter.
[3] For details on this file's grammar, see the document File Grammar Guide's section alias.xml.
[4] For more details on encodings, see Character Sets and Encodings.
[5] For details on this file's grammar, see the document File Grammar Guide's section alias.xml
[6] For an example of declaration of aliases for data types, see Alias of Data Types.