Table of Contents
- 6.1. Basic Method Specification
- 6.2. Intra-Entity Communication
- 6.3. Sending Auxiliary Data to the Client Via Proxy
- 6.4. Exposed Methods ‐ Client-to-Server Communication
- 6.5. Implicit
set_Methods<property_name> - 6.6. LOD on Methods
- 6.7. Inter-Entity Communication
- 6.8. Mailboxes
- 6.9. 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 which execution context they will be executed within.
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 Implicit
|
||
The grammar for method declaration is described below:
<[ClientMethods|CellMethods|BaseMethods]> <method_name> <Exposed/> <Arg> data_type </Arg> </method_name> </[ClientMethods|CellMethods|BaseMethods]>
‐ 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 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, we would have the lines
below:
<root>
...
<CellMethods>
<yell>
<Arg> STRING </Arg> <!-- phrase to exclaim -->
</yell>
</CellMethods>
...
</root>
‐ Declaration of cell method
<res>/scripts/entity_defs/<entity>.defyell
By convention, the meaning of each argument is written next to it as an XML comment.
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
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
Auxiliary data can be streamed to the client via the proxy, without affecting the normal game traffic. Proxy data is opportunistically streamed to the client when bandwidth is available.
All data types in the proxy data are user-defined, since BigWorld does not have any internal uses for it.
Data is added to a proxy via method
addProxyData:
id = Proxy.addProxyData( id, data )
Where the parameters are:
-
id16-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.
-
dataData to be sent to the attached client. Must be in string format.
Once the client has received the entire data string, the virtual
method onProxyData in the
ServerMessageHandler interface is invoked.
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/>
<Arg> STRING </Arg> <!-- phrase to exclaim -->
</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
<Arg>tag that is automatically filled in with the entity ID of the client calling it.
Client instances actually call the method with one argument less than are 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.
When the server updates a property with distribution flag [6] 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, oldType = None ):
...
‐ Example of an implicit
<res>/scripts/client/Seat.pyset_seatType for the Seat
entity.
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 (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:
BigWorld.entities per
execution context.</title>| 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. |
Entity IDs can be obtained in various ways:
-
From exposed methods (client sends entity ID as argument).
-
From the entity object's
idproperty.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[7]. 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
Guardentities 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).
Mailboxes are used to communicate to 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[8], however it is frequently 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
These properties 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
documentation's entry Main Page → BigWorld (Module) → Member Functions.
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.
[6] For details on property's distribution flags, see Data Distribution
[7] For other methods, please refer to the
BigWorld.Entity
class reference in the Base, Cell and Client Python API
documentation.
[8] For more details, see Inter-Entity Communication
