Difference between revisions of "Game Cloud"

From Virtual World Web Wiki
Jump to: navigation, search
(Avatar System)
(Avatar System)
Line 527: Line 527:
 
! Layer !! Identifier !! Description
 
! Layer !! Identifier !! Description
 
|-
 
|-
! scope="col" | Creature Underlay || layer.creature.underlay || .
+
! scope="row" | Creature Underlay || layer.creature.underlay || .
 
|-
 
|-
! scope="col" | Persona Underlay || layer.persona.underlay || .
+
! scope="row" | Persona Underlay || layer.persona.underlay || .
 
|-
 
|-
! scope="col" | Appearance || layer.persona.appearance || .
+
! scope="row" | Appearance || layer.persona.appearance || .
 
|-
 
|-
! scope="col" | Run-time Appearance || layer.creature.underlay || .
+
! scope="row" | Run-time Appearance || layer.creature.underlay || .
 
|-
 
|-
! scope="col" | Creature Overlay || layer.creature.overlay || .
+
! scope="row" | Creature Overlay || layer.creature.overlay || .
 
|-
 
|-
! scope="col" | Persona Overlay || layer.persona.overlay || .
+
! scope="row" | Persona Overlay || layer.persona.overlay || .
 
|}
 
|}
  

Revision as of 16:19, 17 September 2019

The Game Cloud is a Layer Two component that extends your server to add many common massively multiplayer role playing game (MMORPG) features as well as a suite of server world scripting extensions that simplify game programming.

Game Cloud Scripting

The Game Cloud offers a large extension to the server JavaScript environment. The main API access points are through the two global objects Cloud and Bot. I will provide scripting examples throughout this document. For more information about writing scripts for use in the world see Scripts and the World Scripting Reference.

Game Cloud Applications

The Game Cloud groups configuration and data storage into containers called Applications. An application can be developed, configured, and tested on your development server, and then published to your live server when you're ready. All of the application's associated objects move with the application as a single unit when transferred.

Developing an Application

Game Cloud applications are created and configured mostly through the Admin Web. Expand Game Cloud on the left navigation menu, and select Applications. Create a test application to get started. See Identifiers and Scope below for advice on selecting a good identifier for your application.

Once created, you can begin creating other Game Cloud objects (features) that will become part of your application. The objects you create will then be available for your world scripts to interact with. More complete examples of how this works will be given below.

Identifiers and Scope

The Game Cloud makes heavy use Identifiers which are strings of text that uniquely identify some object within a scope. Your application will have an identifier, as will most of the things you create within your application.

Good identifiers should use namespacing to help ensure they are unique. Identifiers should not contain spaces. I recommend you use a format like this:

company.application.feature.name

For Example

vww.ref.quests.firstquest

This helps ensure that things created by different companies or teams don't interfere with each other.

Generally speaking, identifiers are global in scope. This means they should be unique across all applications on your server. This is because an application groups objects for transfer, but does not provide a naming container. This is so that scripts and features from one application can interact with scripts and features from another application.

Data Storage and Scope

The Game Cloud offers several ways for your scripts to store and retrieve data. These are explained in Metadata and Data Storage sections. Data storage is always associated with (scoped to) an application. When a script wants to work with application data, it must first set an application context like so:

Cloud.SetApplication("vww.ref.apps.example");

Generally, application data is considered to be "server local". This means that your application's stored data is not transferred between servers with the application. This is because the data stored by your scripts represents the runtime state of your world. What is transferred is the ``configuration`` of your Game Cloud features and also any Metadata stored with your application.

Application Features

Metadata

Application Metadata is a simple way for you to set some key / value pairs in the admin that will be available to your script at run-time. These values cannot be changed by your scripts. You can think of these as configuration values. They are configured on your application's Summary tab.

Your scripts must first set their application context, and then can query these values like so:

Cloud.SetApplication("vww.ref.apps.reference");
 
var message = Cloud.GetMetadata("StartupMessage");
 
Debug.Log("Starting up with message: " + message);
Chat.GetLocalChannel().Broadcast(message);

The example above shows how the value of the StartupMessage metadata item can be retrieved and used to broadcast a chat message into the instance local chat. For more information see Cloud Global, Chat Global, ChatChannelExtended and Debug Global in the World Scripting Reference.

Data Storage

Application Data Storage allows your scripts to store and retrieve key / value pairs associated with different objects. There are three different stores of application data:

Application Global Data: Stored globally for your entire application. Their keys must be unique within the scope of your entire application. Use the DataExists, GetData, SetData and DeleteData methods on the Cloud Global object.
Persona Scoped Data: Stored for each Persona (avatar). This allows you to store information on a specific character in the game world. Their keys must be unique withing the scope of a single persona. Use the DataExists, GetData, SetData and DeleteData methods on any ParticipantExtended object.
Account Scoped Data: Stored for each user account. This allows your application to store some value shared among all Personae (avatars) of a user, so no matter which avatar they're logged in as, these values will be available. Their keys must be unique withing the scope of a single user account. Use the AccountDataExists, GetAccountData, SetAccountData and DeleteAccountData methods on any ParticipantExtended object.

Your scripts must first set their application context, and then can work with these values like so:

Cloud.SetApplication("vww.ref.apps.reference"); // set your application context
 
var maxPlayers = Number(Cloud.GetMetadata("MaxPlayers"));
Log.WriteDebug("Starting up. Maximum Players: " + maxPlayers);
 
Instance.OnEnter = function(/*ParticipantExtended*/ part){
    // A new participant has joined the scene
    if(Cloud.DataExists("PlayerCount")){
        var count = Number(Cloud.GetData("PlayerCount")) + 1;
        if(count >= maxPlayers) {
            part.SendChatMessage("Sorry, only " + maxPlayers + " players allowed.");
            return;
        }
        Cloud.SetData("PlayerCount", String(count));
        part.SendChatMessage("Welcome, there are " + count + " players.");
    }else{
        Cloud.SetData("PlayerCount", "1");
        part.SendChatMessage("Welcome, you are the only player.");
    }
 
    part.SetData("Playing", "true");
    Chat.GetLocalChannel().Broadcast(part.Name + " joined the game!");
 
    if(part.DataExists("PlayCount")){
        var plays = Number(part.GetData("PlayCount")) + 1;
        part.SetData("PlayCount", String(plays));
        part.SendChatMessage("You have played before.");
    }else{
        part.SetData("PlayCount", "1");
        part.SendChatMessage("This is your first play.");
    }
};
Instance.OnLeave = function(/*ParticipantExtended*/ part){
    if(!part.DataExists("Playing"))
        return;
    part.DeleteData("Playing");
    Cloud.SetData("PlayerCount", String(Number(Cloud.GetData("PlayerCount")) - 1));
}

The example above shows a simple data access example that limits the number of players that can be involved in some activity. When a user enters the room the OnEnter event fires. We check if the global application data contains a PlayerCount key/value pair, and if so, we increment it, otherwise we initialize it to 1. If there have reached the configured maximum number of players, we abort, otherwise we execute similar logic to keep track of how many times this particular player has played our game. We mark the player as "Playing" so that when they leave we can decrement the global PlayerCount. This code does not demonstrate working with account scoped data, but it works the same as the persona scoped data, except as explained above you use the the GetAccountData, SetAccountData, AccountDataExists, DeleteAccountData methods on ParticipantExtended.

Debugging Data Storage

You can view and manipulate stored data via the Admin Web. Application global data can be found on the App Data tab of your application in the Game Cloud Applications section of the admin. Account and Persona scoped data can be found by looking up the account or persona, and then selecting Data Entries from the Game Cloud tab-drop-down.

Dependencies

You can mark any Layer One object as a dependency of your application. This serves two purposes: First, it ensures the object will be transferred with your Game Cloud application when it's transferred between servers. So if your application expects certain scenes, object types, to be available, you can make sure they're copied with your app. Second, it allows you to specify an identifier so the object can be more easily referenced from script. Many Game Cloud methods take object type IDs (GUIDs) as parameters. These are difficult to recognize, type, and work with. Dependencies help with this.

For example, let's suppose you wanted to instantiate a Primitive Cube object with Object Type ID 82c5194e-814b-11e8-a953-000d3af7ccfb into the root of the scene:

Cloud.Instantiate(Instance.Scene, "82c5194e-814b-11e8-a953-000d3af7ccfb");

That's great, but hard to read, and you can't be sure this object will be available on the server. If you create a dependency for this object type, and give it a name, let's say vww.ref.dep.primitivecube, then we can refer to it in script by using the Cloud.Map method, which maps an identifier to its object GUID.

Cloud.Instantiate(Instance.Scene, Cloud.Map("vww.ref.dep.primitivecube"));

This object type (the Primitive Cube) is now guaranteed to be transferred between servers along with your application. You can also always change which object an identifier refers to without having to update your code, since your code is no longer directly referencing GUIDs.

Groups

You can mark a Layer One Entity Group as important to your application. It will be included as a dependency of your application and transferred between servers along with your application. You can then refer to this group by its identifier from script. For example, if you added the "Admin" system group to your application and gave it an identifier like vww.ref.groups.admin, you could then refer to it from script like so:

Instance.OnEnter = function(/*ParticipantExtended*/ part){
    // A new participant has joined the scene
    if(part.CheckGroup("vww.ref.groups.admin")){
        part.SendChatMessage("Welcome back. You have admin access.");
    }
};

Please note: you cannot use the JoinGroup or LeaveGroup methods of the ParticipantExtended to affect groups that are marked as protected in the admin. This prevents a script from making a user an Admin, or revoking a user's developer role for example.

Abilities

Abilities are a powerful feature of the Game Cloud. An ability is something the user can do, some action they can take. Abilities are actually defined by Layer One, but they're exposed for easy scripting and used heavily by the Game Cloud.

An ability has the following properties:

Identifier used to refer to the ability within the Game Cloud
Ability Identifier Used to create the Layer One ability. The Ability Identifier is a translation string, and becomes the display name of the ability in the UI.
Ability Group Places the ability into the ability tree in some group. Groups can help to enable / disable groups of related abilities at once and help with visual grouping in the UI.
Cooldown The number of seconds before the ability can be used again.
Icon Resource An image used for the ability on a quickbar or ability book in the UI.
Metadata Name value pairs that are delivered along with the ability to the client, and help the UI decide how to display them.

Ability Groups, which can also be defined on the Abilities Tab of your application allow you to create visual groupings of abilities in the UI.

There are 2 ways that a user can be given abilities from the Game CLoud:

Interactions

Interactions are a way of associating abilities with objects or NPCs in the scene. Generally, you specify a CSS Selector which selects objects in the scene you want to be interactable, and then specify which ability to attach. There are two ways to do this, either globally for all participants in the scene, or directly to a specific participant. So for example, you could specify that all boxes in the room, should have an "open" interaction, for everyone. Or you could specify that one specific participant should be able to "sit" on chairs in the scene.

Here's a simple example:

/**@type {InteractionMappingExtended}*/
var interaction = Cloud.RegisterInteraction(".openable", "vww.ref.abilities.open");
 
interaction.OnInteraction = function(/*ParticipantExtended*/part, /*DOMObjectExtended*/ target){
    part.SendChatMessage("You open the box. There's nothing inside.");
    Log.WriteDebug("The user clicked on box with ID " + target.ID);
}

When creating an interaction this way you can also optionally specify a group identifier to which the user must belong for the Interaction to be visible. You can also optionaly specify a CSS Selector which must match the participant's DOMController node in the scene. This allows for all kinds of clever ways of setting up who can see what interactions. See Cloud Global for more information on Cloud.RegisterInteraction.

You can also use ParticipantExtended's RegisterInteraction method to register an interaction visible to only one participant.

Engagements

When a user enters various states in an engagement, they can have specified abilities added, removed, enabled or disabled. This is helpful for making the special abilities available during an interaction, situation or quest of some kind.

For more information see Engagement System below.

Tokens

Tokens are virtual currencies. For example, you could create a currency called "Eggs", and then let a user collect them. Hide eggs around your world, and when a user finds one, grant them an egg token. Then allow the user to "spend" their eggs somewhere to get a cool prize. Tokens are a way to implement things like money systems in an MMORPG, or a simple counter for a game or promotion.

When you create a token you can specify it's identifier (which can be translated), its short "Code" which is like USD, CAD, RUB, or EGG in our example, and whether the token is Account or Persona scoped. This last one is important, it specifies if the token is meant to be counter per character (Persona) or shared across all characters for a login (Account).

Once created you can use your new currency like this:

Cloud.SetApplication("vww.ref.apps.reference"); // set your application context
 
var initialEggs = Number(Cloud.GetMetadata("InitialEggs"));
Log.WriteDebug("Starting up. InitialEggs: " + initialEggs);
 
Instance.OnEnter = function(/*ParticipantExtended*/ part){
    if(!part.DataExists("InitialGiven")){
        var token = Cloud.GetCurrency("vww.ref.tokens.eggs");
        token.Payout(part, initialEggs);
        part.SetData("InitialGiven", "True");
        part.SendChatMessage("Gave you " + initialEggs + " Eggs to get started");
    }
};

You can view a user's token balances on their Account or Persona pages in the admin.

Events

Events are one of the foundations of the Quests, Achievements, and Locks systems. An event is simply an identifier that you can fire from script. Quests can listen for instances of an event being fired, and that can cause a quest step to move ahead. Achievements can wait for an event to be fired a certain number of times. You can see how many times a user has fired any event by visiting their persona page in the admin and navigating to the Game Cloud tab and the Achievements & Events section.

Instance.OnEnter = function(/*ParticipantExtended*/ part){
    part.FirePlayerEvent("vww.ref.events.enteredcloudscene");
};

Important Note: When Quests or Achievements complete for a user, the system automatically fires an event with the same identifier as the quest or achievement. You'll see below how this allows you to make quests and achievements depend on each other (use each other's completion (or non completion) as a condition or objective).

Quests

The Quests system provides everything you need to get started building exciting things for your users to do. The system is simple, but very extensible. Put simply, a quest is a series of steps that must be completed in order. You can create a quest on the Quests tab of your Game Cloud application in the admin.

Quests have identifiers, and can be initiated, checked, and abandoned (cancelled) from script. When a quest completes it automatically fires an event with the same identifier. This means quests and achievements can listen for each other's completion events, and use them as requirements or objectives.

Let's start with a simple example. You can see this example in action in the /ref/cloud scene, and its configuration can be examined in the admin by looking up the vww.ref.quests.reference quest in the vww.ref.apps.reference Game Cloud application.

Our quest has two steps: First you must leave and re-enter the /ref/cloud scene 5 times. Then you must click on the bot titled "Click Me!" 5 times.

There are 3 bots (NPCs) in the room, each tagged with one CSS Selector "starter", "stopper", or "clicker", are used to interact with the quest simply by clicking on them. One bot gives you the quest (starts it), the second bot can stop the quest for you (abandon it), and the last bot emits vww.ref.events.click events that the quest can respond to.

Here's all the code you need running in your scene to make this quest work:

/**@type {DOMGeometryExtended}*/
var starter = Instance.Scene.QuerySelector(".starter");
/**@type {DOMGeometryExtended}*/
var stopper = Instance.Scene.QuerySelector(".stopper");
/**@type {DOMGeometryExtended}*/
var clicker = Instance.Scene.QuerySelector(".clicker");
 
starter.OnClick = function(/*ParticipantExtended*/part){
    if(part.HasQuest("vww.ref.quests.reference"))
        part.SendChatMessage("You already have the quest!");
    else
        part.StartQuest("vww.ref.quests.reference");
}
stopper.OnClick = function(/*ParticipantExtended*/part){
    if(!part.HasQuest("vww.ref.quests.reference"))
        part.SendChatMessage("You don't have have the quest!");
    else
        part.AbandonQuest("vww.ref.quests.reference");
}
clicker.OnClick = function(/*ParticipantExtended*/part){
    part.FirePlayerEvent("vww.ref.events.click");
}

Now let's look at all the parts of a quest and how to configure them. Configuration of all of these is done in the admin, then extended by your scripts in the world at run-time.

Quest Basics

When you create a quest, you give it an identifier so it can be referred to from scripts, and a title and description so it can be displayed in the user's quest tracker UI.

You can also enable or disable the quest by setting its enabled flag. Disabling a quest doesn't lose user progress on the quest, it just hides it from people's quest trackers and prevents the quest from being started by new users until it's re-enabled.

You can also choose whether the user should see a pop-up dialog with the quest title and description and have to confirm that they want to accept the quest. For most quests, you'll want to leave this enabled, but for a welcome or tutorial quest you may not want the user to be able to ignore it.

Quest Conditions

Quest conditions help you a set of requirements that the user must pass before the quest will be available for them. You can call the EligibleForQuest method on the ParticipantExtended object to find out if a user is currently eligible to start a quest. You can add multiple conditions to a single quest. There are several types of conditions:

Condition Type Description
Never Completed This particular quest can not have been completed by this user in the past. Good for one-time only quests.
Creature Type The user belongs to a certain creature type (male, female, cat, robot, elf, whatever you have).
Entity Group The user belongs to some Game Cloud defined group of users (see Groups)
Lock State The user has unlocked (or not unlocked) some Game Cloud lock (see Locks)
Event Fired The user has fired some Game Cloud event some number of times. This takes into account historical firings of the event. Remember that finishing quests or achievements causes an event to fire that matches their identifier. You can use this to make this quest require another to be completed (not not be completed).

Quest Steps

Steps represent the different parts of a quest. Quest steps must be completed in order. You can check what step of a quest a user is on from script by calling the GetQuestStep method on the ParticipantExtended object. Steps have a title and description which are displayed in the quest tracker, and also an optional script name which is what's returned from GetQuestStep.

Each Step of a quest has Objectives which must be completed before the step is completed and progress moves forward. A step also has Effects which are powerful features of the quest system that affect a user's gameplay while on a quest. See more on these next.

It is normal to have a reward step as the last step in your quest that has no objectives, but simply executes a bunch of effects that give the user rewards.

Quest Step Objectives

Quest Objectives tell the player what they need to do to move to the next step of the quest. There are two kinds of objectives:

Objective Type Description
Fire Event The user must fire the named event some number of times. Remember that finishing quests or achievements causes an event to fire that matches their identifier. You can use this to make a quest step that requires another quest or achievement to be completed.
Break Lock The user must unlock some feature. (see Locks)

Quest Step Effects

Quest Effects are powerful features of the quest system that affect a user's gameplay while on a quest. Each effect is notified when the user enters or exits the quest step, and is also notified when the user logs back on and is still on this quest step. There are several kinds of effects:

Effect Type Description
Fire Event Fires an event for the user when they first enter this step. This can be used to cause other quests or achievements to advance.
Give Item Places an instance of an Object Type in the user's (see Inventory). You can also specify if the object must not already be in their inventory, and which creature type this effect applies to (so you can give different rewards for different creatures).
Consume Item Removes an instance of an Object Type from the user's (see Inventory).
Wear Causes a reference to an Object Type to be attached to the user's avatar while they're on this quest step. Note: this can be anything, like an object type containing a DOMScript node which is attached to the user's avatar while on this quest step. You can also use it to simply apply clothing to the user's avatar.
Spawn Object Causes a reference to an Object Type to be placed in the instance where the user is when this step is entered. Note: this can be anything, like an object type containing a DOMScript node that affects the room, or a simple special effect.

You can also specify metadata key/value pairs that will be converted into properties and placed on the object's root node when it's placed in the instance. If properties with the same name already exist, the Game Cloud will attempt to convert your value to the same data type. Color32 values can be specified in HTML format (#RRGGBB). Point3D values can be specified as a JSON string like so {x:0.0,y:0.0,z:0.0}

Spawn Object sets the ParticipantID property on the spawned object's root node to be the user's Persona ID. Scripts in your spawned object can use this ID to find the user's ParticipantExtended object by calling Insatnce.GetParticipants() and then finding the one with a matching PersonaID property.

Spawn Object also sets the ParticipantControllerID property on the spawned object's root node to be the user's avatar DOMController node ID. This can be helpful for finding the user's avatar in the room.

Give Currency Gives some amount of some Token or system currency to the user. (see Tokens)
Join Group Joins a user to a a group, optionally removing them from that group when leaving the step. (see Groups) This could be used to grant the user access to something or control the functioning of other scripts. Since groups can also be used as quest conditions, this could cause new quests to become available, although Locks would probably be better for this behavior.

Achievements

The Achievement system compliments the Quest system by providing long term goals a player can work towards. You can create an achievement on the Achievements tab of your Game Cloud application in the admin.

Achievements have identifiers, and when completed, automatically fire an event with the same identifier. This means achievements and quests can listen for each other's completion events, and use them as requirements or objectives.

Achievements have a title and description, as well as a group name which are all displayed in the UI. In Curio, press "Y" key by default to open the achievements UI.

You can assign a points value to each achievement. Users can compare their relative total achievement points.

Achievement Requirements

At present there is only one kind of achievement requirement, although more could be added in the future. Once all of the requirements for the achievement have been met, the achievements will be awarded to the user.

Requirement Type Description
Fire Event The user must fire the named event some number of times. Remember that finishing quests or achievements causes an event to fire that matches their identifier. You can use this to make a quests and achievements depend on each other.

Please Note: The achievement system looks at the total number of named event firings ever by the user. This means that if you create a new achievement based on some event that's already being fired regularly in the system, a user may see the achievement already partly completed when it first appears in their list. This means you can fiddle with achievement requirements without interrupting users' current progress.

Also Note: Once an achievement has been completed for a user, it stays completed even if you change the requirements later.

Dialogs

The Dialog system enables simple conversations with NPCs (bots), or a way to present some information to the user. Dialogs have identifiers so they can be started from script, and provide callbacks to the script when the user moves the conversation along (makes a choice from some options).

Here's a simple example

Instance.OnEnter = function (/*ParticipantExtended*/ part) {
    part.Dialog("vww.ref.dialogs.welcome", function(/*Number*/choiceIndex){
        if (choiceIndex == -1)
            part.SendChatMessage("You closed the window!");
        else
            part.SendChatMessage("You chose item " + (choiceIndex + 1));
    }, "Welcome Message");
}

You can customize the dialog window by adding an image that's displayed above the dialog text. The script that calls the dialog can also override the default window title of "Conversation Dialog", for example, to specify an NPC name, so the player knows who's talking to them.

You can also provide an audio file (in OGG format) which will be played on the "Dialogue" audio channel when the window opens. This is great for voice-overs and guided tours.

Locks

The locks system provides a simple unified way to put content and features behind a paywall or require some achievement by the user before content or features can be used.

Locks work much like Quest Steps or Objectives. You define a set of steps the user must take to unlock something. For example, the user might have to upgrade to a paid membership, and then complete a quest, and then pay some game currency to "unlock" some extra inventory space.

Locks work closely with both server scripts and UI scripts to show the user that something is locked, what they'll need to do to unlock that thing, and in some cases to provide links or tools to do that. The Game Cloud provides a client UI script that is sent to the Curio very early in user's session and which stays up to date with the Game Cloud component about which locks are opened. This script can be queried from your UI scripts to find out if a lock is opened, and if not, to request a callback if it ever becomes opened (all steps copmpleted). You can also ask the locks UI script to display a pop-up with more information about the lock requirements.

Lock Steps

There are a few types of lock steps.

Step Type Description
Fire Event The user must fire the named event some number of times. Remember that finishing quests or achievements causes an event to fire that matches their identifier. You can use this to make a lock depend on quests and achievements, or any other event.
Join Group The idea here is that a user is required to become a paid member or reach some "level" represented by them being a member of a group. If they're not in the group, you can provide a URL either relative to the Web View root, or an absolute URL, which will be launched in the user's browser if they press the button associated with this step. You can set the button text as well.
Pay Currency The user can be required to pay some amount of some Token or other system currency to remove the lock (or at least to satisfy this step). Note: It's best to place this type of step as the last step so the user doesn't pay for something they can't actually get yet. So if you require a user to be upgraded (Join Group), and then to pay some in-game tokens, make sure the tokens come after the upgrade requirement.

Locks UI Script Example

Here's a simple example of a class LockListener that communicates to the Game Cloud lock manager. It sends a GC.LockManager.Reload script message to the waiting lock manager on startup. The lock manager in turn responds with a GC.LockManager.Proxy script message containing a reference to itself (or it's proxy). With this handshake complete, our LockListener can now subscribe to updates events about the Lock. In turn the LockListener provides its own event (get onLockChanged()) and an accessor for the event manager as well (get lockManager()).

Finally, we have an example of a class that uses the LockListener, MyUIWindowHandler. We demonstrate how you can show locked UI until a change in the lock occurs.

class LockListener {
    constructor() {
        this._lockManager = null;
        this._lockChanged = new EventHandler();
        EventManager.OnStart.AddListener(this.onLoad, this);
        EventManager.OnShutdown.AddListener(this.onReleaseEvents, this);
    }
    static get current() {
        if (!LockListener.hasOwnProperty("_current"))
            LockListener._current = new LockListener();
        return LockListener._current;
    }
    onLoad() {
        Script.EventManager.OnLocalScriptEvent.AddListener(this.onScriptMessage, this);
        Script.Broadcast(JSON.stringify({command: "GC.LockManager.Reload"}));
    }
    onScriptMessage(message, obj) {
        try {
            let msg = JSON.parse(message);
            switch (msg.command) {
                case "GC.LockManager.Proxy":
                    Debug.Log(`msg.command: ${msg.command} => ${obj}`);
                    if (this._lockManager !== obj.proxy) {
                        this.onReleaseEvents();
                        this._lockManager = obj.proxy;
                        if (this._lockManager != null) {
                            this._lockManager.onUpdated.AddListener(this.onLockUpdated, this);
                            this._lockManager.onUnlocked.AddListener(this.onLockUnlocked, this);
                            this._lockManager.onRemoved.AddListener(this.onLockRemoved, this);
                        }
                    }
                    break;
            }
        }
        catch (e) {
            Debug.Log(e);
        }
    }
    onReleaseEvents() {
        if (this._lockManager != null) {
            this._lockManager.onUpdated.RemoveListener(this.onLockUpdated, this);
            this._lockManager.onUnlocked.RemoveListener(this.onLockUnlocked, this);
            this._lockManager.onRemoved.RemoveListener(this.onLockRemoved, this);
        }
    }
    get onLockChanged() {
        return this._lockChanged;
    }
    get lockManager() {
        return this._lockManager;
    }
    onLockUnlocked(identifier) {
        //Debug.Log(`onLockChanged "${identifier}"`);
        this._lockChanged.RaiseEvent(identifier);
    }
    onLockUpdated(identifier) {
        //Debug.Log(`onLockUpdated "${identifier}"`);
        this._lockChanged.RaiseEvent(identifier);
    }
    onLockRemoved(identifier) {
        //Debug.Log(`onLockRemoved "${identifier}"`);
        this._lockChanged.RaiseEvent(identifier);
    }
}
LockListener.current;
 
// Now your code can do things like this:
 
class MyUIWindowHandler {
    constructor() {
        this.lockIdentifier = "vww.ref.locks.example"
        // On start-up, check if our lock is still locked
        if (LockListener.current.lockManager.isLocked(this.lockIdentifier)) {
            // If so we should listen for changes to this lock
            LockListener.current.onLockChanged.AddListener(this.onLockUpdated, this);
            // and do something here to display that this feature is locked in your UI
        }
   }
 
    lockedUIClickedHandler() {
        // When a user clicks on a locked section of your UI, check that it's still locked.
        if (LockListener.current.lockManager.isLocked(this.lockIdentifier))
            // If so then display the lock tool window
            LockListener.current.lockManager.showLockInfo(key, s.WorldRect);
    }
 
    onLockUpdated(identifier) {
        if (LockListener.current.lockManager.isLocked(this.lockIdentifier))
            return;
        // It's unlocked! do something here to remove the lock display
    }
}

The Player Object

The Game Cloud adds an Extension to the Layer One ConnectionIdentity object which represents a connected user. The extension (PlayerIdentityExtension) adds the concept of "Player", as in, someone playing a game. The player extension is then exposed to server JavaScript as the PlayerExtended type and is accessible via the GetPlayer() ParticipantExtended. This object lets you interact with the various gaming features of the Game Cloud (Quests, Achievements, Locks, etc). Most notably, it has events that fire when the user progresses on a quest, achievement, or lock.

Instance.OnEnter = function (/*ParticipantExtended*/ part) {
    var player = part.GetPlayer();
    player.OnUnlock = function function (/*ParticipantExtended*/ part, /*String*/ identifier) {
        Log.WriteDebug("Unlocked: " + identifier);
    }
}

Avatar System

The Creature Manager component provides a powerful system for building and customizing avatars of any kind. It can be used to build player avatars, Non Player Characters (NPCs) or bots, monsters & enemies, and more. The Game Cloud extends this system and exposes it to JavaScript.

In a nutshell the Creature Manager avatar system does these things:

  • Manages the life time of Avatar instances in running Instances.
  • Starts with a base Object Type as a prototype, it builds the avatar by populating hookpoints on the avatar.
  • Maintains a set of layers each obscuring the one below, and together contributing to fill the avatar's hookpoints, enabling various run-time effects.

There is a hierarchy of types of avatar, each one adding more specific functionality:

Class Exposed As Description
Avatar AvatarExtended The simplest type of avatar. Just loads an Object Type. Creates one default layer RuntimeAppearance meant to contain throw-away changes to the avatar as it lives out its life in an Instance.
CreatureAvatar CreatureAvatarExtended An avatar based on a creature type. The Object Type to load as the body of the avatar is taken from the creature config. If the creature config specifies an animation controller Object Type, a reference to it will be added as well. Two default layers are added: Underlay which is meant to contain base clothing that is always present under everything else (eg: underwear, creature type specific tattoos or body adornments, etc.), and Overlay which is meant to override everything, adding creature specific decorations as final touches. These two layers are shared among all avatars of the specified creature type. This is very powerful, and allows changes to all the avatars of a given creature type on the server at once.
PersonaAvatar PersonaAvatarExtended An avatar meant for a player, representing a specific PersonaID. Adds three more default layers: Another persona-specific Underlay and Overlay which work like the Creature Avatar's except only affect avatars of this persona, and also an Appearance layer which represents the users saved clothing choices (what they're wearing).
PlayerAvatar PlayerAvatarExtended An avatar created for an InstanceParticipant, representing a real player avatar in an Instance.

A typical Player Avatar, therefor, will have the following layers:

Layer Identifier Description
Creature Underlay layer.creature.underlay .
Persona Underlay layer.persona.underlay .
Appearance layer.persona.appearance .
Run-time Appearance layer.creature.underlay .
Creature Overlay layer.creature.overlay .
Persona Overlay layer.persona.overlay .

Engagement System

Inventory

Usable Items

Other Helpful Utilities

Bound Objects

Bound objects are simply a way to connect an Object Type to Worldspace so that whenever an Instance of a Scene is created, a Reference to the object is inserted into the DOM at the scene node. What's especially handy is that you can define an Area Group, add several several Scenes to it and then use this feature to cause any Object Type you like to be added to the scenes at run-time. You could also, for example, bind an Object Type to your system's Root World to cause it to show up in every scene in your world. This is especially helpful for Object Types that contain scripts that you want to appear in many (or all) scenes.