Table of Contents
- 6.1. Basic Method Specification
- 6.2. Two-way calls
- 6.3. Service Methods
- 6.4. Intra-Entity Communication
- 6.5. Bandwidth Optimisation: Send Latest Only
- 6.6. Bandwidth Optimisation: Is Reliable
- 6.7. Sending Auxiliary Data to the Client Via Proxy
- 6.8. Exposed Methods ‐ Client-to-Server Communication
- 6.9. Server to Client bandwidth usage of Method calls
- 6.10. Client callbacks on property changes
- 6.11. LOD on Methods
- 6.12. Inter-Entity Communication
- 6.13. Mailboxes
- 6.14. Method Execution Context
Methods allow events to be propagated, both between different execution contexts of an entity (i.e., cell, base, client), as well as between different entities. BigWorld separates entity methods into categories based on the execution context within which they will be executed.
In general, methods should not be used for propagating states. The use of properties is recommended for this purpose. For example, a player holding a gun should be a property, while a player shooting should be a method.
The categories of methods are:
Category | Runs on | Common uses |
---|---|---|
<BaseMethods> |
BaseApp |
Updates properties on the base. Serves as a root point to propagate messages to related things. |
<CellMethods> |
CellApp |
Notifies the cell of changes in response to player interaction. Allows communication between nearby entities. |
<ClientMethods> |
Clients |
Notifies the client of events, so that
the player can see them. Implicit
|
[a] For details, see Client callbacks on property changes |
Method categories.
The grammar for method declaration is described below:
<[ClientMethods|CellMethods|BaseMethods]> <method_name
> <Exposed/> <Args> <arg1_name
>data_type
</arg1_name
> <arg2_name
>data_type
</arg2_name
> </Args> <ReturnValues> <ret1_name
>data_type
</ret2_name
> <ret2_name
>data_type
</ret2_name
> </ReturnValues> <!-- Only send one call to clients if called repeatedly --> <SendLatestOnly> [true|false] </SendLatestOnly><!-- Send the call reliably --> <IsReliable> [true|false] </IsReliable>
</
method_name
> </[ClientMethods|CellMethods|BaseMethods]>
For details, see Bandwidth Optimisation: Send Latest Only. |
|
For details, see Bandwidth Optimisation: Is Reliable. |
‐ Method declaration
syntax<res>
/scripts/entity_defs/<entity>
.def
All methods in all categories have some fundamental common
characteristics. They are declared in the relevant section in the file
,
with an XML tag per method.
<res>
/scripts/entity_defs/<entity>
.def
The method's arguments and return values (if any) are also defined in the file, and its types are specified in the same way as property types. For more details, see Property Types.
In order to declare a method called yell
on
the cell, which receives a string argument named
phrase
, we would have the lines below:
<root> ... <CellMethods> <yell> <Args> <phrase> STRING </phrase> <!-- phrase to exclaim --> </Args> </yell> </CellMethods> ... </root>
‐ Declaration of cell method
<res>
/scripts/entity_defs/<entity>
.defyell
Once the method is declared, it also needs to be declared in the appropriate Python implementation file. Each context of execution (cell, base, and client) has a folder containing scripts for each entity.
In our example, the method was added to the section
<CellMethods>
, and therefore will be executed
on the cell entity.
The cell script for this entity, named
,
will need to define the <res>
/scripts/cell/<entity>
.pyyell
method, as
illustrated below:
import BigWorld
...
class <entity>
(BigWorld.Entity):
def __init__(self):
BigWorld.Entity.__init__(self)
def yell(self, phrase):
# insert code to implement yell here
return
‐ Definition of cell method
<res>
/scripts/cell/<entity>
.pyyell
Remote two-way method calls can be used to get return values from
Python calls between processes. Return values for entity methods are
declared in the same format as the method arguments in the entity
definition file, except that they are listed under the
ReturnValues
tag instead of Args
. For example,
the webCreateAuction
Base method for an Avatar
has the following definition:
<webCreateAuction> <Args> <itemSerial> ITEMSERIAL </itemSerial> <expiry> UINT32 </expiry> <startBid> GOLDPIECES </startBid> <buyout> GOLDPIECES </buyout> </Args> <ReturnValues> <auctionID> AUCTIONID </auctionID> <ReturnValues> </webCreateAuction>
This method takes a number of arguments containing the details of the item being listed for auction, and returns the unique auction ID. However, this method makes a remote call to an entity that is potentially on another process, which prevents us from receiving a return value in the usual way. Instead, this method will return a Twisted Deferred object, that will send the return values as an argument to a callback method once they are received. This entire process is explained in Twisted Deferred Objects.
To obtain the return value of a two-way method, Twisted Deferred objects are used. When a method is called, a Deferred object is returned. Two types of callback methods can then be added to this object: a callback for successful calls, and an errback for when an an error has occured. When the method has been executed, it will call one of the two callback methods, depending on the success of the call. It is also possible to chain more than one callback for both success and failure.
For a more detailed explanation about how
Deferred
objects are used, see the Twisted
documentation at http://twistedmatrix.com/documents/current/core/howto/defer.html.
As an example, we will implement
remoteTakeDamage
, a Base method for an Avatar
that causes it to lose some health, and returns its remaining health.
This method will call takeDamage
, one of the
Avatar's Cell methods.
takeDamage
will take the damage amount as
its argument, and return two values: the entity's remaining health, and
its maximum health. These values will be printed, and the remaining
health amount will be returned from
remoteTakeDamage
as the Deferred object's its
final value.
Since the Base entity and Cell entity for the Avatar are on different processes, the call will use Deferred objects to effectively achieve an asynchronous two-way call. The methods will have the following definitions in the entity definition file, Avatar.def:
... <BaseMethods> <remoteTakeDamage> <Args> <damage> INT32 </damage> </Args> <ReturnValues> <health> INT32 </health> </ReturnValues> </remoteTakeDamage> ... </BaseMethods> <CellMethods> <takeDamage> <Args> <damage> INT32 </damage> </Args> <ReturnValues> <health> INT32 </health> <maxHealth> INT32 </maxHealth> </ReturnValues> </takeDamage> ... </BaseMethods>
remoteTakeDamage
has the following
implementation in
fantasydemo/res/scripts/base/Avatar.py
:
# base/Avatar.py from twisted.internet import defer ... def remoteTakeDamage( self, damage ) deferred = self.cell.takeDamage( damage ) deferred.addCallback( partial( self.onDamageTaken, damage ) ) return deferred def onDamageTaken( self, damage, healthArgs ): health, maxHealth = healthArgs[ 0 ], healthArgs[ 1 ] print "Avatar 's' took %d damage, reducing its health to %d/%d" % \ (self.basePlayerName, damage, health, maxHealth) return (health,)
Since the call to takeDamage
is
asynchronous, we will not receive the return value immediately. Instead,
a Deferred object will be returned, referred to by
deferred
. Once a result is available, it will be
stored in the Deferred object, taking the form of a tuple containing the
return values. Since we are unable to receive and use the values
immediately, we can specify a method to be called using the values as an
argument, once they become available.
The addCallback
method is used to specify
a method that will be called once this result is available. This method
will be given the result tuple as its argument. In our example, once the
result is available in the Deferred object, it will be sent as the final
argument to the onDamageTaken
method. If
additional callback methods are specified using
addCallback
, they will be called in turn. The
result from each one will be stored as the new result in the Deferred
object, and sent as the argument for the next callback method in the
list. This is called a 'callback chain'.
When there are no remaining callback methods in the chain, the final result will be stored in the Deferred object. At this point, any additional callbacks that are added will be called immediately, since there is already a result available.
Finally, the result of onDamageTaken
will
be stored in the Deferred object, and sent to the caller of
remoteTakeDamage
.
Methods can also be specified to be called if a remote method call
fails. This is done using the addErrback
method, for example:
deferred.addErrback( onError
)
If an error object is returned from the call, it will be used as
the argument to
.
onError
Errbacks can be chained in the same way as callbacks, by using
multiple calls to addErrback
. The
addCallbacks
method adds both a callback and an
errback to a deferred object, but it has slightly different behaviour
than adding the two separately using
addCallback
and
addErrback
.
addCallbacks
adds both methods at the same
level in the call chain, whereas adding them separately will put each
method on its own level. This is explained in detail in the Twisted
documentation, at http://twistedmatrix.com/documents/current/core/howto/defer.html.
The implementation of takeDamage
, in
cell/Avatar.py
, is as follows:
# cell/Avatar.py import CustomErrors from twisted.internet import defer ... def takeDamage( self, damage ): if (damage < 0): # Goes to errback which return defer.fail( CustomErrors.InvalidFieldError( "Avatar can not take negative damage" ) ) self.health = self.health - damage if (self.health < 0): self.health = 0 # Goes to onDamageTaken callback return (self.health, self.maxHealth)
Methods that are called in this way must return either a tuple
containing the return values, or a Deferred object. If a tuple is
returned from takeDamage
, its values will be
stored in deferred
, in the
remoteTakeDamage
method, and sent as arguments
into its callback method, onDamageTaken
, as
described above. However, if a Deferred object is returned, its result
tuple and any callbacks specified for it will be propagated to
remoteTakeDamage
's deferred
object, and the onDamageTaken
callback will be
added to the end of the chain.
For examples of remote methods that create Deferred objects, add
callbacks and errbacks to them and return them, refer to the web
interface Base methods in FantasyDemo's Avatar
and AuctionHouse
entities, located at
fantasydemo/res/scripts/base
.
In our implementation of takeDamage
, an
error results in a CustomErrors.InvalidFieldError
object (a custom error derived from Exception
) to
be returned using defer.fail
. This method
creates a special error object: a Deferred object that has already had
an errback called. Using this method is a simple way of returning an
error to the original caller. Once the error object is received by the
original caller, it will be used as the argument for the first errback
method, if there is one. Instead of calling
defer.fail
, an exception can be raised:
raise CustomErrors.InvalidFieldError( "Avatar can not take negative damage" ) )
This will have the same result as returning the error object using
defer.fail
, and will also send the call stack
for the error to MessageLogger.
In this example, we use CustomErrors
objects. These custom errors are derived from
BWError
, and are the recommended way to send
errors in two-way calls. For details about custom errors, refer to Error objects.
If a two-way call fails, it will send an error object to the
Deferred object, and invoke the first errback method, if there is one
specified. This object is an instance of an
Exception
.
A number of error types deriving from our custom exception class
BWError
are defined in
bigworld/res/scripts/server_common/BWTwoWay.py
.
These can be extended to suit the errors that could come about in the
game scripts. Two subclasses of BWError
are
defined: BWStandardError
, which is primarily used
as a base class for errors arising in the server binaries, and
BWCustomError
, which is used to as a base class
for errors occuring in the game scripts.
Error objects originating in the server binaries are derived
from the BWStandardError
type, and are declared
in
bigworld/res/scripts/server_common/BWTwoWay.py
.
These error types are as follows:
-
BWAuthenticateError
A player can not be authenticated with the credentials they provided.
-
BWInternalError
The server experienced an error that was caused internally.
-
BWInvalidArgsError
A method is called with invalid arguments.
-
BWMercuryError
No response was received for a request.
-
BWNoSuchCellEntityError
A cell entity can not be found.
-
BWNoSuchEntityError
An entity can not be found.
New error types should be implemented to allow errors to be as
helpful as possible. BWCustomError
should be
used as the base class for a range of error classes specific to the
game scripts. For example, FantasyDemo defines a number of custom
error classes for its two-way calls specific to its auction house
implementation, such as InsufficientGoldError
and InvalidItemError
. For a description of each
of the custom error types used in FantasyDemo, refer to the Server Web Integration Guide's sectionPHP Error handling.
Custom errors are declared in
.
The above implementation of <res>
/scripts/server_common/CustomErrors.pyremoteTakeDamage
shows how a custom error can be returned as the deferred object
instead of the return values:
return defer.fail( CustomErrors.<Error Type>
(<Args>
) )
Methods for services are defined in the much same way as base, cell
and client methods. However, there are some slight differences, since,
unlike the other categories, services are not entities. Service methods
are declared in the file
,
with an XML tag per method.
<res>
/scripts/service_defs/<service>
.def
Service methods fall under the Methods
tag, instead of
under a specific category such as BaseMethods
or
CellMethods
.
In order to declare a method called addNote
on the service, which receives a string argument named
newNote
, we would have the lines below:
<root> ... <Methods> <addNote> <Args> <newNote> STRING </newNote> </Args> <ReturnValues> <noteID> NOTE_ID </newNote> </ReturnValues> </addNote> </CellMethods> ... </root>
‐ Declaration of service method
<res>
/scripts/service_defs/<service>
.defaddNote
Once the method is declared, it also needs to be implemented in the service Python implementation file. The service execution context has a directory containing the scripts for each service.
The script for this entity, named
,
will need to define the <res>
/scripts/service/<service>
.pyaddNote
method, as
illustrated below:
import BigWorld
...
class <service>
( BigWorld.Service ):
def addNote( self, description ):
# insert code to implement addNote here
return
‐ Definition of service method
<res>
/scripts/service/<entity>
.pyaddNote
Different execution contexts of an entity communicate with each other by calling methods on the other execution contexts. These are exposed as special properties of the entity.
As a quick reference, the available objects are described below:
-
allClients ‐ Available on: Cell
When/how to use: To call a client method on all client instances of this entity.
Example: self.allClients.someMethod()
-
base ‐ Available on: Cell, Client
When/how to use: To call a base method of this entity. Calls to this object are executed on the base script. The client cannot directly call methods on the base of other entities.
Example: self.base.someMethod()
-
cell ‐ Available on: Base, Client
When/how to use: To call a cell method of this entity. Calls to this object are executed on the cell script. All client instances can access their cell object (when the entity exists on the cell).
Example: self.cell.someMethod()
-
otherClients ‐ Available on: Cell
When/how to use: To call a client method on all client instances of this entity, except on its own.
Example: self.otherClients.someMethod()
-
ownClient ‐ Available on: Cell, Base
When/how to use: To call a client method only on this entity's client. This object calls the method only on the entity on the client application that is 'playing' as this entity, not on other client applications that can see this entity.
Example: self.ownClient.someMethod()
The methods of a nearby entity can be called from the client directly on the cell part of that entity.
BigWorld automatically exposes these objects to the relevant script classes.
What this means is that it is possible for any script that is part
of an entity (cell, base or client part) to call other scripts that are
part of the same entity. The definition file
(
)
describes which methods are exposed to different execution
contexts.
<res>
/scripts/entity_defs/<entity>
.def
When a method is called on Entity.otherClients
(or Entity.allClients
), 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[7], 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 calls of a single method to be sent in a single update.
If the SendLatestOnly flag is set on a client method, only the latest call is kept in the event history. This avoids sending multiple calls. This can save bandwidth being sent to the client and can save some memory on CellApps if a method is called frequently and only the latest call is needed.
This value is false by default.
Properties with the OTHER_CLIENTS flag also have this flag. See Bandwidth Optimisation: Send Latest Only.
When a method is called on Entity.otherClients
(or Entity.allClients
), a message is sent to the
appropriate client applications that have this entity in their
AoI[8]. By default, these messages are sent reliably. If the
IsReliable flag is set to false on a
client method, this message will be sent unreliably.
Properties with the OTHER_CLIENTS flag also have this flag. See Bandwidth Optimisation: Is Reliable.
Auxiliary data can be streamed to the client via the proxy, without affecting the normal game traffic. This data is opportunistically streamed to the client when bandwidth is available.
The bandwidth used by auxiliary data streaming can be controlled
using the bw.xml
options in
<baseApp/downloadStreaming>. For details of this
option group refer to the Server Operations Guide
section BaseApp Configuration Options.
All data types in this streamed data are user-defined, since BigWorld does not have any internal uses for it.
Data is added to a proxy via method
streamStringToClient
:
id = Proxy.streamStringToClient( id, data )
or via method streamFileToClient
:
id = Proxy.streamFileToClient( id, resource )
Where the parameters are:
-
id
16-bit ID of the data. If -1 is received, then the next ID in sequence that is not currently in use is selected. The caller of this method is responsible for the management of this parameter. The same ID value used by the method is returned to the caller.
-
data
Data to be sent to the attached client. Must be in string format.
-
resource
The string name of the resource to be sent to the client.
Once the client has received the entire data string, the callback
BWPersonality
.onStreamComplete
is called on the client.
Because MMOGs operate over the Internet, and in order to stop players cheating, server methods (those on the cell or the base) are not automatically allowed to be called by the client.
In order to make a server method callable from the client (so that
the world can provide interactivity), its declaration has to include the
tag <Exposed/>
, as illustrated below:
<root> ... <CellMethods> <yell> <Exposed/> <Args> <phrase> STRING </phrase> <!-- phrase to exclaim --> </Args> </yell> </CellMethods> ... </root>
‐ Declaration of the exposed
method<res>
/scripts/entity_defs/<entity>
.def
The tag <Exposed/>
accomplishes two
things:
-
It makes the method available to clients.
-
On the cell, it acts as an additional argument tag that is automatically filled in with the entity ID of the client calling it.
Client instances actually call the method with one argument fewer than the number received by the cell entity, which prevents 'entity-faking' outside the safe server environment. The entity ID needs to be passed as an argument when calling exposed methods from the server components.
The definition of the method on the cell must be extended to take this parameter, as illustrated below:
import BigWorld class EntityName(BigWorld.Entity): def __init__(self): BigWorld.Entity.init(self) def yell(self, sourceEntityID, phrase): # insert code to implement yell here # if desired, check that self.id == sourceEntityID before proceeding return
‐ Definition of cell method
<res>
/scripts/cell/<entity>
.pyyell
On the client, the method yell
can be
called with the code below:
self.cell.yell( "BigWorld message test" )
Example of a client calling a cell method yell
If the cell method yell
implements the
check of sourceEntityID
against
self
.id
, only its client will be
able to call it. Others clients will not be able to execute it.
There might be occasions where the method might run with a different
sourceEntityID
. For example, for a method called
shakeHand
, it is a good idea to check that the
source entity is within a couple of metres away from the
self
entity before proceeding (and maybe that neither
is dead).
Script writers should be aware that the arguments of any exposed method need to be heavily scrutinised on the server side before being operated on.
The underlying C++ code that handles the passing of arguments ensures that the method will be invoked on the server side only if:
-
The right number of arguments is being passed.
-
The arguments have the expected type.
Beyond that, however, the underlying architecture cannot provide any further constraints on the values that clients may pass to method calls on the server.
For example, integers may take any valid 32-bit value, strings and arrays may be of any length, …. It is up to the script writer to ensure that the arguments to an exposed method have values that make sense in the context of that method.
You should carefully inspect the value of any arguments to an exposed method before using them.
It is never safe to trust arbitrary Python objects from the client
as passed in through PYTHON
parameters to exposed method
invocations. A WARNING log message is emitted at
startup by any component that parses the entity definitions if an
exposed method has parameter of type PYTHON
. The safer
alternative is to use some other data type that is more concretely
defined, for example the FIXED_DICT
data type, and validate
each known element.
The arguments of a method call made from the server to the client are optimised in the same way property updates are optimised.
When the server changes a property on the client a callback method is called on the entity to allow the client to take appropriate action.
set_
is
called when a property of an entity is replaced with a new value. That is,
a value in the entity's <property_name>
__dict__
is replaced.
If an existing property is modified, either
setNested_
or
<property_name>
setSlice_
is
called.
<property_name>
When the server updates a property with distribution flag
[9] ALL_CLIENTS,
OTHER_CLIENTS or OWN_CLIENT, an
implicit method called
set_
is called on the client.
<property_name>
This method should not need be declared in the section
<ClientMethods>
of the definition file as it
is automatically provided by BigWorld.
All implicitly defined
set_
methods have one argument which receives the old value of the property
when the method is called. For example:
<property_name>
class Seat( BigWorld.Entity ): ... def set_seatType( self, oldValue ): ...
‐ Example of an implicit
<res>
/scripts/client/Seat.pyset_seatType
for the
Seat
entity.
Note that if an existing property is modified instead of being
replaced and the appropriate
setNested_
or
<property_name>
setSlice_
method does not exist, this method is called with the
<property_name>
oldValue
argument as None
.
If a nested property of an existing property is modified, the
setNested_
is called. This includes modifying a single element of an
<property_name>
ARRAY
or FIXED_DICT
. The method accepts two
arguments. The first represents the path to the change and the second is
the value that has been replaced.
For example:
class Seat( BigWorld.Entity ): ... def setNested_myFixedDict( self, path, oldValue ): ...
The first argument represents the path to the changed property. For example, if the change was:
self.myFixedDict.rightHandItem.weight = 10
The value of path would be:
["rightHandItem", "weight"]
If you had a property that was ARRAY <of> ARRAY
<of> INT32 </of> </of>.
self.myArray[ 5 ][ 3 ] = 8
would result with the value of path being:
(5, 3)
If a slice of an array is modified, the
setSlice_
is called. This includes appending and deleting from an array. The
method accepts two arguments. The first represents the path to the
changed slice and the second is the slice that has been replaced. The
last value in the path is a tuple containing two integers. These
represent the range of the new values. To create a slice containing the
new values, use <property_name>
myModifiedArray[ path[-1][0] : path[-1][1]
]
.
For example, if self.myArray
is an existing array
with 5 elements:
self.myArray.append( 10 )
would result in setSlice_myArray
being
called with:
path = [(5,6)] oldValues = [ ] newValues = self.myArray[ path[-1][0] : path[-1][1] ]
If the array is part of a FIXED_DICT
and the array
has 5 elements with the last value 21.
del self.myFixedDict.myArray[-1]
would result in:
path = ["myArray", (4,4)] oldValues = [21]
Like properties, some methods need to be broadcast only to nearby entities.
For example, even though an entity may be visible at an AoI distance (say, 500 metres), it seems unlikely that players will be able to tell the difference between a smiling and a non-smiling entity.
To reflect his, the smile method's level of detail, can be declared as illustrated below:
<root> ... <ClientMethods> ... <smile> ... <DetailDistance> 30 </DetailDistance> </smile> ... </ClientMethods> ... </root>
‐ Declaration of client method
<res>
/scripts/cell/<entity>
.pysmile
The specification of the LOD for a method is far simpler than it is for a property. This is because a non-broadcast property change might have to be sent later if the LOD increases, while a non-broadcast method in the same scenario will not have to.
Consequently, the method just needs to declare a tag
<DetailDistance>
inline, and when it is called
on exposed objects allClients
or
otherClients
, it is broadcast only to clients within
the specified distance around the entity.
Once the entities have methods assigned to them, it becomes useful to be able to call methods on other entities.
If you have an object ent
as a Python script
representation of another entity, then you can call
ent
.someMethod
() to call that
method on ent
. This assumes that
someMethod
() runs on the same execution context
you are in. For example, if you are on the cell, then
ent
.someMethod
() must be
defined and provided by the entity type of ent
.
If you are on the cell, you can call a method on the base, with the code below:
ent.base.otherMethod()
This means that once you are able to obtain an entity object, there is a plethora of options for invoking methods on different execution contexts of different entity instances. For more details, see Intra-Entity Communication.
But to achieve this, first it is necessary to retrieve the object for another entity. The mechanism for that is described in the next sub-section
In order to uniquely identify every object in the game universe, BigWorld assigns a unique number to every entity. This is referred to as the entity ID.
In the BigWorld
Python module (which is
imported at the start of most scripts), there is an object which maps
entity IDs to the corresponding entity object. This object has the same
interface as a Python dictionary, and is called
BigWorld
.entities
.
Given an entity id entityID
, its entity object
can be retrieved with the code below:
ent = BigWorld.entities[ entityID ]
Retrieval of entity object using its ID
One should be very careful with entity IDs, and always check for
the existence of the corresponding entity before assuming that it is
safe to use it.
BigWorld
.entities
throws an
exception if the entity looked up does not exist.
Each execution context has a version of the
BigWorld
.entities
object with
different entities in it.
BigWorld
.entities
contains the entities that are relevant to the execution context in
which it is located, as listed below:
Execution context | Entities listed in
BigWorld .entities |
---|---|
Cell | Real and ghosted entities located on all cells managed by this cell's CellApp. |
Base | Entities located on this base's BaseApp [a]. |
Client | Entities in the client's AoI. |
Entities listed in
BigWorld
.entities
per
execution context.
Entity IDs can be obtained in various ways:
-
From exposed methods (client sends entity ID as argument).
-
From the entity object's
id
property.You can pass the object's ID from one execution context to another (e.g., from the client to the cell), so that the other context can obtain the corresponding object. You can also use this property to obtain the ID of a newly created entity.
-
From various utility methods that one can use to find entity references.
One of these methods is
entitiesInRange
.[10]This method is defined for every cell entity, and returns all entity objects located within a certain distance from the calling entity. The resulting output can be queried again for more specific search results.For example, to find all the
Guard
entities within 100 metres of the current entity, you could have the code below:def findGuards(): output = [] for entity in self.entitiesInRange( 100 ): if entity.__class__.__name__ == "Guard": output += [entity] return output
‐ Obtaining ID of surrounding entities<res>
/scripts/cell/<entity>
.pyCare should be taken when using this approach for entity discovery as it is a linear search, and hence does not scale well. Specifically to be avoided are searches over large entity sets that could be obtained from a search of large distances, or from using the complete set of entities in
BigWorld
.entities
. For a small distance however, one would not expect large numbers of objects (although this depends on the game).
While an entity is identified by its entityID
,
a service is identified simply by its name, regardless of how many
ServiceApps are offering it. Each Service instance is referred to as a
service fragment.
In the BigWorld
Python module, there is an
object which maps the name of a service to the corresponding service
fragment on one of the ServiceApps that offer it. This object is called
BigWorld.services
and, like
BigWorld.entities
, it has the same interface as a
Python dictionary.
The service
can be
retrieved using the code below:
serviceName
serviceMailbox = BigWorld.services[ serviceName
]
Retrieval of service using its name
A service retrieved in this way will be a mailbox to the corresponding service fragment. For details about mailboxes, refer to Mailboxes. Once this mailbox is retrieved, the service's methods can be invoked. For example:
BigWorld.services[ 'ExampleService' ].myTest( u"This is a test" )
Mailboxes are used to communicate with entities that are remote, i.e., not on the current process.
Entities can only access other entities that are running in the same execution context[11]However, it is often useful to be able to send a message to entities in other execution contexts. For example, an entity may wish to send a message to all members of a chat channel, but the channel might not have all its members located on the same BaseApp as the executing entity.
Mailboxes are used to implement the following properties of an entity:
-
self
.cell
-
self
.base
-
self
.ownClient
There are also a special set of mailboxes available for use from the cell which reference other entities in relation to the current entity. These mailboxes are discussed in Special Mailboxes.
These afore mentioned properties (cell
,
base
, ownClient
) allow you to
reference objects located on different processes, and can be sent like any
other value, using method calls, and the MAILBOX data
type.
You can use the MAILBOX like a normal entity reference, and call methods declared in the entity's definition file on other entities on other processes.
Considering that an entity B (referenced in the
following example as anotherEntity
) has a method
heyThere
which takes one argument of type
MAILBOX, an entity A can pass its
base mailbox to entity B from the cell, as in the
example below:
anotherEntity.heyThere( self.base )
‐ Passing base mailbox from
cell<res>
/scripts/cell/<original_entity>
.py
On the receiving entity B (referenced in the
example as anotherEntity
), the calling entities (entity
A) base mailbox (as received via the method argument)
can be used to call a method someMethod
on entity
A. For example:
def heyThere( self, originalEntity ): originalEntity.someMethod()
‐ Receiving base
mailbox<res>
/scripts/cell/<another_entity>
.py
It is also possible to store mailboxes in a class as properties. However this is only useful for base entity mailboxes, since they will not change address as the entity moves around the space.
Cell entity mailboxes should not be stored, because they can change as the entity moves between cells. It is possible to pass cell entity mailboxes to another entity for once-off use, since they are guaranteed to be usable for the time it takes to call a method and have it respond (i.e., up to approximately 1 second).
Using mailboxes, it is also possible to call methods within a
different execution context to that of the mailbox being called. For
example, using a base mailbox of an entity (baseMB
in
the following code), it is possible to call a cell method of the base
entity as follows:
baseMB.cell.cellMethod()
Calling a cell method of another entity through its base mailbox
The call is sent to the base entity first, which then calls the cell method from where the base entity resides. Though it is more convenient than calling a base method that in turn calls the cell method, it still takes two hops to call the cell entity.
Available usages are:
-
baseMB.cell
-
baseMB.ownClient
-
cellMB.base
-
cellMB.ownClient
These are in fact instances of MailBoxes, and can be passed as method arguments. However, the same restriction applies to cellMB.base and cellMB.client as to cell mailboxes ‐ they can change as an entity moves around, and therefore should not be stored for later use.
Note that both baseMB.ownClient and cellMB.ownClient refer to their own client only. There are no shortcut calls to otherClients and allClients.
A convenient way to obtain other entities' mailboxes is by using the
methods
BigWorld
.lookUpBaseByDBID
and
BigWorld
.lookUpBaseByName
on the BaseApp. For more details, see the BaseApp Python API's entry Main Page
→ BigWorld (Module) → Member Functions.
On top of the previously discussed mailboxes, the CellApp also offers a special set of mailboxes which can be used to communicate with entities within the AoI of the reference entity.
The available mailboxes are:
-
.entity
allClients
-
.entity
otherClients
-
.entity
clientEntity(
EntityID
)
The allClients
mailbox will call a method on
all client entities that exist in the AoI of the reference entity as
well as on the reference entity's ownClient
.
The following diagram illustrates a call to the allClients mailbox
on Entity A such as A.allClients.chat()
. This results in
the chat()
method being called on Entity A on
ClientApps A, B and C.

The otherClients
mailbox, is almost identical
to the allClients
mailbox, except that the method
being called will not be executed on the reference entity's
ownClient
. This is useful for situations where the
reference entity's client was the initiator of the action.
The following diagram illustrates a call to the otherClients
mailbox on Entity A such as A.otherClients.jump()
. This
results in the jump()
method being called on
Entity A on ClientApps B and C.

The most complicated mailbox is clientEntity(
. This mailbox is used to
call a method on an entity that is represented on the reference entity's
ClientApp. This is useful for causing a behaviour that is specific to a
single Client to only be seen by the interested client, such as a quest
giver talking to the player.
EntityID
)
The following diagram illustrates a call to the clientEntity
mailbox on Entity B providing the EntityID of C such as
B.clientEntity( C.id ).wave()
. This results in the
wave()
method being called on Entity C on
ClientApp B.

All entity method calls across physical machines are asynchronous. For example, if you execute self.cell.cellMethod() or self.client.clientMethod() on a base entity, the call returns immediately without any value. The actual method execution takes place on the machine where the cell or client part of the entity resides.
To inform the calling entity of the execution result you will have to call a function from within the method.
The example below describes the client entity of class Avatar initiating a sequence of actions to open a door, executing the following steps:
-
The client method openDoor of class Avatar calls its cell method openDoor.
-
That method then calls the cell method unlock of class Door, passing self as an argument. This way, Door receives a mailbox of the cell entity Avatar, which is later used (with exposed object client) to call the appropriate cell method on Avatar.
-
That method then checks the keycard, and using the cell mailbox (sourceEntity), makes a call directly to the appropriate client method of class Avatar (in this example, we assume it was successful).
-
The client entity of the Avatar class:
class Avatar( ): ... def openDoor( self, doorID ): # Call the cell method to open the door self.cell.openDoor( doorID ) ... def doorOpenFailed( self, doorID, keycard ): # Animation shows the Avatar scratching his head ... def doorOpenSucceeded( self, doorID, keycard ): # Animation shows the door with corresponding doorID opening ...
<res>
/scripts/client/Avatar.py ‐ Definition of door methods
-
The cell entity of the Avatar class:
class Avatar( ): ... def openDoor( self, doorID ): # locate the door door = self.locateTheDoor( doorID ) keycard = self.getKeycardFromInventory() door.unlock( self, keycard ) ...
<res>
/scripts/cell/Avatar.py ‐ Definition of methodopenDoor
-
The cell entity of the Door class:
class Door( BigWorld.Entity ): ... def unlock( self, sourceEntity, keycard ): # check source is close enough # check keycard is good if not self.isGoodKeycard( keycard ): sourceEntity.client.doorOpenFailed( self.id, keycard ) else: self.isOpen = True sourceEntity.client.doorOpenSucceeded( self.id, keycard ) ...
<res>
/scripts/cell/Door.py ‐ Definition of methodunlock
The same callback technique is used to return values from a called method.
The example below describes the client entity of class Avatar initiating a sequence of actions to inquire about an item's description based on its inventory index, executing the following steps:
-
The client method investigateInventory of class Avatar calls its base method itemDescriptionRequest.
-
That method then calls its client method itemDescriptionReply.
-
That method then displays the item's description.
-
The client entity of the Avatar class:
class Avatar( ): ... def investigateInventory( self, indexInInventory ): # first get the details from the server self.base.itemDescriptionRequest( indexInInventory ) # maybe have a timeout in case server doesn't reply ... def itemDescriptionReply( self, indexInInventory, desc ): # call the callback if desc == []: GUI.addAlert( "No such item" + str(indexInInventory) ) return GUI.displayItemWindow( indexInInventory, desc ) ...
Example
<res>
/scripts/client/Avatar.py ‐ Definition of inventory methods
-
The base entity of the Avatar class:
class Avatar( ): ... def itemDescriptionRequest( self, indexInInventory ): try: desc = self.generateDescription( indexInInventory ) except: desc = [] # in case no such index self.client.itemDescriptionReply( indexInInventory, desc ) ...
Example
‐ Definition of<res>
/scripts/cell/Avatar.pyitemDescriptionRequest
For those entities residing in the same process, the method calls take place synchronously. However, since there is no guarantee that the calling entities and the called ones will always be in the same process, it is better to adopt the callback solution.
A special case is an entity method that is not defined in the
entity's definition file
(<res>
/scripts/entity_defs/<entity>
.def
‐ For details, see The Entity Definition File). In this case, the method is always
executed synchronously, and is only executed in the process of the caller.
For example, a method call on a ghosted entity normally will be delegated
to the real one. However, if this method is not defined in the entity's
definition file, it will be treated as a usual Python method, and run
locally.
This mechanism does save a bit of network traffic between server components, and can return a result immediately to the caller, but it is also limited in that it can only access the read-only ghosted properties. Trying to access non-ghosted properties or to write read-only properties would result in unexpected errors. Unless carefully planned, one should not take advantage of this feature.
[7] For more details on how the AoI priority queue works, see Player AoI Updates.
[8] For more details on how the AoI priority queue works, see Player AoI Updates.
[9] For details on property's distribution flags, see Data Distribution
[10] For other methods, please refer to the
BigWorld
.Entity
class reference in the Base, Cell and Client Python API
documentation.
[11] For more details, see Inter-Entity Communication