Creating Instanced Areas

From ALFA
Jump to: navigation, search

The ACR supports an "Instanced Area" system that allows multiple copies of an area to be instantiated at runtime, based on a "template" area that you design in the toolset. The template can be any standard area, except its content and associated scripting logic should be aware that there might be multiple instances of the area.

Area instances are useful if you want to provide multiple, isolated copies of an area. One use might be to instance an area based on player party, for example, so multiple parties get their own private "copy" of an area. Other uses might be for player housing, player "travel areas" (like a caravan or a vehicle interior), certain special spells like "Rope Trick", or anywhere else that creating multiple copies of the same area might be valuable.

Anything that happens in one instance doesn't directly impact other instances.

There isn't any hard limit on how many instances of an area can be created, although there is some memory overhead. The overhead on the server is much less than creating static copies of an area in the toolset, though, because the server just re-uses the walkmesh information from the template area when the instancing system is used.

Script code is needed to manage the creation of area instances and moving players into an area instance (as a static transition will only go to one instance). There are a series of script functions in the acr_area_instance_i header that are used to create and manage area instances.


Area Instance Lifetimes

It is currently not possible to delete an area instance once it has been created. As a result, the area instancing system keeps a "cache" of instances that have been created for a given template area. When a new instance is requested with ACR_CreateAreaInstance, the instancing system first checks whether there was a previously created instance; if so, it is used. Otherwise, a brand new area instance is set up.

The first time a brand new area instance is created, all of the statically placed objects are spawned (if you had any). Subsequently, if the area instance is cleaned up and reused, then statically placed objects are assumed to be in the right state. It's a good idea to use the spawn system and not statically place objects that are destroyable by the player.

In order to deal with this, areas that are instanceable should be designed to be "resettable", that is, returned to their default state by script code. There is a local variable attached to the template area, ACR_AREA_INSTANCE_ON_DELETE_SCRIPT, which names a script to run when an area is eligible to be cleaned up. The job of this script is to determine whether the instance should be cleaned up or not, and if so, to "reset" the area to a pristine state. For example, the script might delete all spawned creatures or treasure on the area so that the next player using the area gets a pristine state. Once the on delete script has cleaned up an area instance, a future call to ACR_CreateAreaInstance can return that same area object. It's your responsibility to delete any unwanted objects that might be lingering in the instance from the area cleanup script.

Areas have an associated on create script that is designated with a local variable on the area template, ACR_AREA_INSTANCE_ON_CREATE_SCRIPT. This script is called every time ACR_CreateAreaInstance secures an area to return. The area might be a previously reused cached instance, or a brand new instance. The on create script gives you a chance to set up any state that needs to be created when an area is going to be used.

Instanced area cleanup can be handled either automatically or by script code. By default, area instances are automatically cleaned up after 60 seconds To enable automatic cleanup, set the ACR_AREA_INSTANCE_CLEANUP_DELAY variable to something other than -1 (or leave it at the default of zero to trigger the default time of 60 seconds). This variable specifies a delay (whole or fractional count of second) after which an area instance that is empty of all players will be automatically cleaned up.

When an area is going to be cleaned up, either automatically or by an explicit call to ACR_ReleaseAreaInstance, the on delete script is called. If the on delete script returns FALSE, then the area isn't cleaned up (and if automatic cleanup was enabled, the cleanup script will be called again after the cleanup delay to check if the area should be kept alive). Otherwise, the area is made available for future use once the cleanup script returns.


Getting Into (And Out of) an Area Instance

Normal areas often have static connection points in the form of area transitions that link to an area. This doesn't work as normal for an instanced area, though, as there might be multiple copies of an area instance active at a given time.

You will need to write some script code to transfer the player into an appropriate area instance (possibly creating one if necessary), when an instance is called for. This code might run as a part of a dialog or transition onuse script; it would typically find an appropriate already existing area instance (say the one used by the party leader), or create a new area instance with ACR_CreateAreaInstance. Then, this script code would jump the player into an appropriate location within the area.

Another option might be to have an area OnClientEnter script that checks if the entering player is supposed to be in a particular instance of an area (maybe with a local variable set on the PC object to designate this), if the current area is not an instance as returned by ACR_IsInstancedArea. If the player had an instance, the script could move the PC into the instance, otherwise it could create an instance for the PC and move the PC into that instance. That would let you continue to use a conventional static transition into the area (but you'll area transition twice, which is a bit slower, when entering it).

Remember that waypoints are relative to an area instance, so if you want to move a player into an area by jumping to a waypoint, you'll have to find a way to get the waypoint for the right area instance. One way to do this would be to have the area on create script find the waypoint in the newly created area instance, and store it as a local object variable on the area instance object. Then, you could use GetLocalObject() to find the appropriate entry waypoint for a given area instance.

Area linkages outside of an area instance will work like normal (as long as they don't link to another area instance, in which case the above rules apply). You might find it useful to provide dynamic linkages in some cases, though, like in an instance travel caravan. These linkages could be customized by the area on create script.

It's a good idea to turn on automatic area cleanup for your area instances. This allows them to be deleted after a "cool down" time once nobody is in the area. A "cool down" time enables automatic area cleanup, and is optional (specified by ACR_AREA_INSTANCE_CLEANUP_DELAY). If enabled, the area instancing system will keep track of when nobody is in the area and after the "cool down" time elapses, the area will be automatically cleaned up. This frees you from having to worry about manually getting rid of your area instances when they aren't in use.

It can sometimes be useful to keep an empty area instance around for a short time, like several minutes. For example, you might want to let a player return to a previously created instance in case they had forgotten something on the ground, if they remember soon enough. It's up to you to design how this should work for your area (and how the player gets back into their old instance).


Instanced area local variables

There are a series of controlling local variables that you can set on an area object that will be used as an instanced area template. Make sure to use the correct case (and variable type) for these variables when you set them in the toolset!

string ACR_AREA_INSTANCE_ON_CREATE_SCRIPT 
This variable gives the name of a script that runs when the area instance is being "created" and will be returned from ACR_CreateAreaInstance(). The script name shouldn't have an extension. It's optional to specify an on create script, but if you need to do some work when the area is going to be used, then the on create script may come in handy.
string ACR_AREA_INSTANCE_ON_DELETE_SCRIPT 
This variable gives the name of a script that runs when the area instance is being "cleaned up". The script name shouldn't have an extension. It's optional to specify an on delete script, but if the area can at all have dynamically created objects spawned or removed from it, then you should provide an on delete script that resets the area to a clean state.
float ACR_AREA_INSTANCE_CLEANUP_DELAY 
This variable expresses the time after which an area instance with no players is automatically cleaned up (unless vetoed by the on delete script). The time is expressed as a count of seconds (fractional values permitted), just like you would use for a DelayCommand time. If the variable is zero or isn't set, then automatic cleanup isn't enabled for the area. If you want to enable automatic cleanup and clean up an instanced area right away, just set the cleanup delay to 1 second.


Area Instance on Create Script

An area on create script (optional) should looks like this:

void main()
{
   object Area = OBJECT_SELF; // The area being created


   // Set up the new area instance as desired.  This
   // example finds the entry waypoint for the area and
   // saves it on the instanced area object so that it can
   // be referred to for jumping players into the area.
   //
   // It is up to you to decide how this should work for
   // your area.  This example uses a waypoint with the
   // tag of "wp_instance_start" that is intended to be
   // placed in the toolset, but you can do that however
   // you would prefer.


   object Obj = GetFirstObjectInArea(Area);
   while (Obj != OBJECT_INVALID)
   {
      int Type = GetObjectType(Obj);


      if (Type == OBJECT_TYPE_WAYPOINT &&
          GetTag(Obj) == "wp_instance_start")
      {
         SetLocalObject(Area, "AREA_START_WP", Obj);
         break;
      }


      Obj = GetNextObjectInArea(Area);
   }


   if (Obj == OBJECT_INVALID)
      WriteTimestampedLogEntry("No start WP for area!");
}

The script is called when ACR_CreateAreaInstance is called. The job of this script is to perform any instance setup tasks that your area instance needs. For example, spawning objects that should be statically spawned, setting up transition links for exit transitions (if they are dynamic), etc.

The example script looks for a waypoint object that is intended to be statically placed in the area template, with the tag "wp_instance_start". It then stores this object as a local variable on the area instance object (which will be returned by GetArea() for objects in the instance). If you wanted to jump a player into the area, to find the right waypoint, you'd call GetLocalObject(AreaInstance, "AREA_START_WP"). You can't just use a tag lookup as the object might be in a different area instance.


Area Instance on Delete Script

An area on delete script (optional) should look like this:

int StartingConditional()
{
	object Area = OBJECT_SELF; // The area being created

	// Clean up spawned objects in the area and remove treasure, etc.
	// It is up to you to decide how what to do here.  This example
	// script just looks for any freestanding items or creatures in
	// the area and deletes them.  You're guaranteed that there are
	// no PCs in the area when the on delete script runs.

	object Obj = GetFirstObjectInArea(Area);
	while (Obj != OBJECT_INVALID)
	{
		int Type = GetObjectType(Obj);

		if (Type == OBJECT_TYPE_CREATURE || Type == OBJECT_TYPE_ITEM)
		DestroyObject(Obj);

		Obj = GetNextObjectInArea(Area);
	}

	// The script can now return TRUE to allow the area to be considered
	// cleaned up (and available for re-use).  Otherwise, the script can
	// return FALSE and the cleanup attempt is dismissed.  If automatic
	// cleanup was enabled, cleanup may run again if the area remains
	// empty, once the cleanup timer elapses next time.

	return TRUE;
 }

The example script scans for any loose creature or item objects in the area, and deletes them. You can perform other cleanup tasks here as appropriate. The general idea is that after the on delete script runs, the area is in the same state as if it were just loaded on module startup (so that it is "fresh" the next time the that instance is used). Don't forget to clean up any local variables on the instanced area object itself that you used to maintain state, if those need to be reset.


Area Instance Management Script Functions

These functions are used to manage the creation or deletion of an area. Typically, script code uses these functions to acquire a new area instance to transport a player into, or to remove an area instance that isn't needed anymore.

//! Create an instanced area.  If an available instance in the free pool can be
//  found, it will be reused.
//!  - TemplateArea: Supplies the area that serves as the template for the new
//                   instanced area.
//!  - CleanupDelay: Supplies the area cleanup delay.  If < 0.1f, then the
//                   value from the design time configuration setting on the
//                   template area is used instead.  If the design time
//                   setting did not specify a value then cleanup is not
//                   enabled for this instance.
//!  - Returns: The area instance, else OBJECT_INVALID on failure.
object ACR_CreateAreaInstance(object TemplateArea, float CleanupDelay = 0.0f);

//! Release an instanced area.  It will be put back onto the free list of the
//  area supports free list pooling, otherwise the area is deleted.
//!  - InstancedArea: Supplies the area instance to release.
void ACR_ReleaseAreaInstance(object InstancedArea);


Area Instance Utility Script Functions

These functions are most useful for scripts that are called in response to events within an area instance. They can be used anywhere, as long as the "InstancedArea" parameter is actually an area instance (for functions that operate on an area instance).

//! Check whether an area is an instanced area or not.  It can be used for the
//  client enter script of a template area, for example, to create an instance
//  of the area and move the entering player to the instance.
//!  - Area: Supplies the area object to query.
//!  - Returns: TRUE if the area is an instance, else FALSE if it is a static
//              created area (which might be the template for an instance, or
//              might be any other static area).
int ACR_IsInstancedArea(object Area);

//! Return the template instance for an instanced area.  This is the area
//  object that had been created from.  Returns OBJECT_INVALID if the area was
//  not really an instanced area.
//!  - InstancedArea: Supplies the instanced area object to query.
//!  - Returns: The template (parent) area, else OBJECT_INVALID if the area
//     was not a valid instanced area.
object ACR_GetInstancedAreaTemplate(object Area);

//! Check whether an area has any player controlled objects in it.  The area
//  can be a normal or instanced area.
//!  - Area: Supplies the area to inquire about.
//!  - ExcludeObject: Optionally supplies an object to exclude from checking.
//!  - Returns: TRUE if the area has no player controlled objects.
int ACR_IsAreaEmptyOfPlayers(object Area, object ExcludeObject = OBJECT_INVALID);