World Instancing

From Multiverse

Jump to: navigation, search

Contents

Overview

World instancing enables you to build a virtual world from multiple world files. You can use instancing to separate disjoint geography, distribute large player populations, or provide private content areas.

An instance is defined by a world file (.mvw) and its collection files (.mwc). You can use a single world file to create multiple instances. Instances are initialized by a pair of script files; one for creation and one for loading. The world and script file names are collected together in an instance template which is used when creating a template.

There is no perceivability between instances; players, mobs, and objects can only "see" within their current instance. Players can move between instances, but can be in only one at a time. When changing instances, the client clears the scene of all current objects, terrain, skybox, and so on, before loading objects in the destination instance.

Instancing APIs are in the multiverse.server.plugins.InstanceClient package.

Examples in this article are written in Python, but apply equally well to Java.

Instance portal examples in Sampleworld

There are several examples of instance portals in Sampleworld. There are examples of:

  • Instancing via object: when the user moves the mouse pointer over the object, a pop-up menu appears showing the instance name. The user can then select it to change into that instance. The barrel with the red decal under it is an example of this kind of portal.
  • Instancing via region: moving into the region initiates an instance change. The region named "meetinghouse_region" is an example of this kind of portal.

In both, cases, you must add special properties to the region or object. In World Editor, select the region or object and then edit the NameValue property in the Property View. For more information on setting properties in World Editor, see World Editor - Adding and editing properties.

These instance portals use the InstancePortal example script provided with Sampleworld.

Portal object

The figure below shows the NameValue dialog box for the barrel that is a portal to the Sampleworld video instance.

Image:Instanceportal1.jpg

In this example, the properties are:

Property name Example value Description
InstancePortal Right click for video instance Mouseover text to display.
instanceName video Name of the instance to which the portal takes characters.
instanceFlags push Special instance flags: If this is "push" or "pop", the portal manipulates the instance stack.
locMarker spawn Name of the marker where characters spawn in the target instance.

Portal region

The meetinghouse_region is an example of a portal region. It transfers users who enter the region into the "bigbrother" instance in Sampleworld. NOTE: The meetinghouse_region is directly on top of the house model, so it is hard to see; you can move the house if you want to see the region in the world view.

For more information on setting properties in World Editor, see World Editor - Adding and editing properties.

Property name Example value Description
OnEnter instanceEntry Specifies that this is a instance entry region trigger. In other words, indicates that this regio is a portal region.
instanceName bigbrother Name of the instance to which this portal transports players.
locMarker spawn Name of the marker where players appear in the target instance.

Creating an instance

To create an instance, create an instance template and optionally an override template.

Defining an instance template

An instance template is required to create an instance. Instance templates are stored in the instance plugin and must have a unique name. The instance template must include a world file and may include an init script and/or load script. The init script is run when the instance is created and the load script is run when a persistent instance is loaded.

In this example, the template uses world file "myworld.mvw" and init script "instance_load.py":

template = Template("my template name")
template.put(Namespace.INSTANCE, InstanceClient.TEMPL_WORLD_FILE_NAME, "$WORLD_DIR/myworld.mvw")
template.put(Namespace.INSTANCE, InstanceClient.TEMPL_INIT_SCRIPT_FILE_NAME, "$WORLD_DIR/instance_load.py") 

rc = InstanceClient.registerInstanceTemplate(template);
Log.debug("Registered instance template: status="+str(rc))

File names in an instance template may contain the following variables:

The template can specify custom properties by adding them to the instance namespace. Instances created with the template will have the same properties. For example:

template.put(Namespace.INSTANCE, "myProperty", "myPropertyValue")

Instance templates are registered with the instance plugin using InstanceClient.registerInstanceTemplate(). Instance templates are typically registered in startup_instance.py which is run once when the instance plugin process is started.

Using a template override

A template override is optional, but is useful to set the instance name (not to be confused with the template name). The template and override are merged to form the instance properties. Use the InstanceClient.createInstance() method to create an instance with a template override. For example:

overrideTemplate = Template()
overrideTemplate.put(Namespace.INSTANCE, InstanceClient.TEMPL_INSTANCE_NAME, "default")
oid = InstanceClient.createInstance("my template name", overrideTemplate)
Log.debug("startup_instance.py: createInstance instanceOid=" + str(oid))

Instance names are optional and need not be unique. You can get an instance OID by name, but if there are multiple instances with the same name, the selected instance is undefined. Instance names are useful when setting a new player's initial location, resolving player location after instance deletion, and moving to standard instances.

An instance is in the loaded state after creation and is ready for immediate use.

Startup instance

The Multiverse server does not create a default or initial instance. Instances can be created or loaded at startup by a "post" script passed to the instance plugin process. By default, the script is named multiverse/config/myworld/startup_instance.py. See the sampleworld multiverse/config/sampleworld/startup_instance.py for an example.

Persistent instances

Instances may be persistent or non-persistent (the default). Persistent instances are saved in the database like any other object. Additionally, the persistent contents of a persistent instance are automatically loaded and unloaded with the instance.

Create a persistent instance as you would other objects, by setting the persistent flag in the object template:

template.put(Namespace.OBJECT_MANAGER, ObjectManagerClient.TEMPL_PERSISTENT, Boolean(True))

Instance persistence must be set when the instance is created. To persist the content of an instance, you must explicitly make the objects persistent.

Managing instances

Once you create an instance, you can load it, unload it, and delete it. Use methods of InstanceClient to manage instances. Do not use the ObjectManagerClient API to manage instances.

Loading an instance

Loading an instance loads the instance object and all its persistent content from the database. When loading is complete, the instance is ready for use. Loading is only available for persistent instances. Use InstanceClient.loadInstance() to load an instance, providing the instance OID as the argument.

Unloading an instance

Unloading an instance unloads all instance content and the instance object itself. Unloaded objects are removed from memory, but the persistent objects are still stored in the database. An unloaded non-persistent instance is effectively deleted. Use the InstanceClient.unloadInstance() method to unload an instance, providing the instance OID as the argument.

Unloading and deleting are equivalent for non-persistent instances.

When an instance is unloaded, a SubjectMessage type InstanceClient.MSG_TYPE_INSTANCE_UNLOADED is published after the instance content is unloaded, but before the instance object is unloaded. The message is published as a broadcast RPC and the caller waits for all subscribers to respond. This message can be used to clean-up resources outside the multiverse distributed object system.

Deleting an instance

Deleting an instance deletes all instance content and the instance object itself from both memory and the database.

Use InstanceClient.deleteInstance() to delete an instance, providing the instance OID as the argument.

When an instance is deleted, a SubjectMessage type InstanceClient.MSG_TYPE_INSTANCE_DELETED is published after the instance content is deleted, but before the instance object is deleted. The message is published as a broadcast RPC and the caller waits for all subscribers to respond. This message can be used to clean-up resources outside the multiverse distributed object system. The message subject is the instance oid.

Entering an instance

Moving from one instance to another can be triggered by the server or the client. The proxy server manages instance entry as follows:

  1. Verify the destination instance exists.
  2. Despawn the player from the current instance.
  3. Change the player's location to the destintation instance.
  4. Spawn the player into destination instance, which becomes the current instance.

Call InstanceClient.objectInstanceEntry() to initiate instance entry, specifying a destination instance OID, location within the instance, and optionally a new orientation. The player's direction is always set to zero after instancing. Specify the location as a marker name within the destination instance.

Restore stack

The instance restore stack maintains a player's previous instance locations. The stack is stored in two properties in the object manager namespace, one for the stack itself and one for the current instance name.

The restore stack is a List of InstanceRestorePoint objects. The bottom of the stack should be a "fallback" location to be used if the upper restore points no longer exist. The character factory must initialize the restore stack with a fallback location.

The proxy server maintains the current instance name. It is used at login time if the player's current instance OID does not exist. The proxy looks up the current instance name to see if the instance was recreated under a different OID. This happens when the world uses non-persistent instances.

To add an entry to the top of the restore stack, specify FLAG_PUSH when calling objectInstanceEntry(). The player's current location (before instancing) is pushed on top of the stack. Optionally, the caller can specify the exact restore location (for example, to restore a player to just outside an instance portal).

You can instance a player to the top (latest) restore point by specifying FLAG_POP when calling objectInstanceEntry(). In this case, the 'instanceLoc' parameter is ignored.

Character creation

Character creation factories must set the player object's initial instance location and configure the instance restore stack.

       # Get the start instance for all new players
       instanceOid = InstanceClient.getInstanceOid("default")
       if not instanceOid:
           Log.error("CharFactory: no 'default' instance")
           properties.put("errorMessage", "No default instance")
           return 0

       # Get the player initial spawn location.  Create this marker in
       # the WorldEditor.
       spawnMarker = InstanceClient.getMarker(instanceOid, "playerSpawn")
       spawnMarker.getPoint().setY(0)

       # Set the player name
       override.put(WorldManagerClient.NAMESPACE,
               WorldManagerClient.TEMPL_NAME, name)

       # Set the player object instance
       override.put(WorldManagerClient.NAMESPACE,
               WorldManagerClient.TEMPL_INSTANCE, Long(instanceOid))

       # Set location within the instance (from the marker)
       override.put(WorldManagerClient.NAMESPACE,
               WorldManagerClient.TEMPL_LOC, spawnMarker.getPoint())

       # Set orientation (from the marker)
       override.put(WorldManagerClient.NAMESPACE,
               WorldManagerClient.TEMPL_ORIENT, spawnMarker.getOrientation())

       # Set persistent flag so player is saved in the database
       override.put(Namespace.OBJECT_MANAGER,
               ObjectManagerClient.TEMPL_PERSISTENT, Boolean(True))

       # Initialize player's instance restore stack
       restorePoint = InstanceRestorePoint("default", spawnMarker.getPoint())
       restorePoint.setFallbackFlag(True)
       restoreStack = LinkedList()
       restoreStack.add(restorePoint)
       override.put(Namespace.OBJECT_MANAGER,
              ObjectManagerClient.TEMPL_INSTANCE_RESTORE_STACK, restoreStack)
       override.put(Namespace.OBJECT_MANAGER,
              ObjectManagerClient.TEMPL_CURRENT_INSTANCE_NAME, "default")

Login processing

When a player logs in, the proxy server uses the player's last known location and the player restore stack to determine the player's initial location. The following steps are taken:

  1. Attempt to load object at last known location.
    The last known location is an instance oid and point in that instance. This will succeed if the instance is already loaded or it is a persistent instance (a persistent instance is automatically loaded when any of its contents is loaded). The player object load will fail if the player was in a non-persistent instance that has been deleted or the server restarted.
  2. Get the player's current instance name, lookup the oid, change player's current location.
    This works when the player was in a well-known, but non-persistent instance. Some instances may always exist (created by server start-up script), but not be persistent.
  3. Attempt to load object at current location.
  4. Pop the top entry from the player's Instance Restore Stack and change player's current location to match.
    If the restore point had an instance name, then the instance oid is taken from instance name lookup.
  5. Repeat from step 3 until the restore stack is empty or the fallback restore point is reached.

Other ways to enter an instance

A player can also enter an instance:

  • Because of a server message
  • Because of a client event
  • Due to moving into a new region

Entry via server message

The server can instance a player at any time by sending an InstanceEntryReqMessage. The InstanceClient.objectInstanceEntry() APIs will do this for you.

Instance entry needs the following information:

  • Player OID
  • Destination instance OID - specified in the BasicWorldNode or by instance name
  • Destination location - specified in the BasicWorldNode
  • optional restore stack manipulations

The version of objectInstanceEntry() that accepts an instance name simply looks up the instance oid prior to sending the message.

Restore stack manipulations are specified with the 'flags' parameter. The possible values are FLAG_NONE, FLAG_PUSH, and FLAG_POP. If FLAG_NONE, then the restore stack is neither consulted nor modified. If FLAG_PUSH, then a restore point is pushed on top of the restore stack. The restore point is either the player current location, or a location specified in parameter 'restoreLocation'. If FLAG_POP, then the top restore point is removed from the stack and the player instanced to that location. The destination parameters to objectInstanceEntry() are ignored in this case.

The flag constants can be found on static class InstanceClient.InstanceEntryReqMessage

# roomName = destination instance name
# playerOid = player to move
# "spawnPt" = marker in destintation instance
instanceOid = InstanceClient.getInstanceOid(roomName)
spawnPt = InstanceClient.getMarker(instanceOid, "spawnPt")
wnode = BasicWorldNode()
wnode.setInstanceOid(instanceOid)
wnode.setLoc(spawnPt.getPoint())
wnode.setOrientation(spawnPt.getOrientation())
wnode.setDir(MVVector(0,0,0))
InstanceClient.objectInstanceEntry(playerOid, wnode, InstanceClient.InstanceEntryReqMessage.FLAG_NONE)

Entry via client event

You may trigger instance entry with a client extension message. The sample assets include a client-side "/instance" command in Script/MarsStandardCommands.py that sends this extension message to instance the player. You must register a hook in the proxy server for this to work, see Proxy Server#InstanceEntryProxyHook.

The client extension message supports all the options available in the server message InstanceEntryReqMessage.

  • instanceName - The destination instance name
  • instanceOid - The destination instance oid
  • locMarker - Marker in the destination instance, this is the spawn point
  • locPoint - (optional) Point in the destination instance (? how is this set on the client)
  • orientation - (optional) Spawn orientation, overrides marker orientation
  • flags - (optional) One of "push" or "pop"
  • restoreMarker - (optional) Marker in the current instance. When flags is "push", push this location on the restore stack (instead of the player's current location).
  • restorePoint - (optional) Point in the current instance. When flags is "push", push this location on the restore stack (instead of the player's current location).
  • restoreOrientation - (optional) Restore orientation, overrides restore marker orientation
 props = {}
 props["instanceName"] = "default"
 props["locMarker"] = "spawn"
 ClientAPI.Network.SendExtensionMessage(0, False, "proxy.INSTANCE_ENTRY", props)

Changing instance when entering a region

You can configure a region to automatically change a player's instance when entered. See: Server Regions - InstanceEntryRegionTrigger

Finding instances

If you know the name of an instance, you can get its Oid via InstanceClient.getInstanceOid()

You can search loaded instances with the Server Object Search API. Use the following search parameters:

  • objectType: ObjectTypes.instance
  • query: instance of PropertySearch
  • selection: instance of SearchSelection with instance property names or the get-all-properties flag enabled.

The search result is java.util.Map objects with the selected properties. SearchSelection flags RESULT_KEYED and RESULT_KEY_ONLY are both supported.

queryProps = HashMap()
# Add matching properties to queryProps
search = PropertySearch(queryProps)

# Get the instance name and instance template name
selection = SearchSelection()
selection.addProperty(InstanceClient.TEMPL_INSTANCE_NAME)
selection.addProperty(InstanceClient.TEMPL_INSTANCE_TEMPLATE_NAME)

# Search using the default format
instances = SearchManager.searchObjects(ObjectTypes.instance, search, selection)
print "1: " + str(instances)

# Get collection of SearchEntry
selection.setResultOption(SearchSelection.RESULT_KEYED)
instances = SearchManager.searchObjects(ObjectTypes.instance, search, selection)
print "2: " + str(instances)

# Get collection of instance oids
selection.setResultOption(SearchSelection.RESULT_KEY_ONLY)
instances = SearchManager.searchObjects(ObjectTypes.instance, search, selection)
print "3: " + str(instances)

Determining an object's instance

A world node contains information about the location of an object in the virtual world including location (x,y,z), orientation, / direction and current instance OID. To determine an object's current instance:

Personal tools