How do I create a persistent node and associate behaviors with it?
Due to the way HeroEngine supports dynamic updates of class structures/hierarchies, the way the engine ties a "script" to a class is through a specific naming scheme. That is: <className> + ClassMethods. So if I were to make a Foo class, I would then create a FooClassMethods script for its behaviors. Scripts that do not follow that naming scheme are just free floating files in which you can implement procedural functions, since HeroScript allows you to freely mix between object oriented and procedural programming depending on which is more suited to a task.
One of the nice features of the DOM Editor is that it has a link to open/create the corresponding class methods script for a class you have defined, so you don't really have to remember the naming scheme at all.
Now defining a class and its behaviors does not actually instantiate an object, its really just definition of the potential for an object. Instantiation is done via one of a variety of external functions (http://hewiki.play.net/wiki/External_functions) which expose the internal workings of the engine to script invocation.
Methods CreateNodeFromClass Create Node From Prototype CreatePersistedNodeFromClass (server) CreatePersistedNodeFromPrototype (server)
var theCwissFooBar = createNodeFromClass( "CwissFooBar" ) theCwissFooBar.Do() // method invocation
Persistence of instances is achieved by creating a node as a persistent instance (through the appropriate external function) and associating it to a "root" hierarchy (Root node and Data storage options).
Why does a trigger remain on the client when I delete the parent node of the trigger on the server?
Triggers that are part of an area are expected to be hard associated in a particular way to the area geometry hierarchy (you may recall we discussed this in passing during training). By changing the parent of your trigger, the server no longer understands the trigger as being a part of the area...but it never received an edit command that would cause it to write out a new DAT file to the repository. You can use HeroBlade's File menu to force a write of the DAT files to fix the area.
Instead of changing the hard association, you can implement the shared function:
// Called prior to destruction of the instance when removed properly via the $EDIT's method _RemoveInstance() shared function _OnInstanceRemoved( instance as NodeRef of Class Instance ) .
In the class methods script for a class I assume you probably are GLOMming onto your parent to provide functionality and in that function call use the $EDIT node to also destroy your trigger. Optionally, if you GLOM onto the trigger instance instead you can have it also destroy the other instance. You should not try to change the hard association.
How do I get rid of orphaned nodes?
When you shut down an area, orphaned nodes are unloaded normally. Since they are not associated to the area, when the area next spins up they do not load. They remain in the database until you run a cleanup on the database that searches for orphaned nodes and purges them. For development, you do not need to concern yourself much about this. You really only run the cleanup as "needed".
How does deleting a node from a replication group affect the rest of the nodes in the group?
If you remove the node from the replication group, all destinations will receive notification on the primary node for the replication group just prior to the destruction of the removed node in the destination.
More information on this can be found at: Replication Script Interface
To programmatically check this implement the following method in the base class of the primary node:
method _OnReplicationNodeRelease(n as NodeRef) as Boolean // Called on primary node when the parameter node is no longer part of replication. Return false to prevention node destruction.
How do I pass information to a trigger that I want it to have when it is introduced to the client?
For information that you want a trigger to have when it is introduced to the client, there is a generic UserData Property in which you can stick any arbitrary string and your script can parse for whatever information you need. The UserData string could be automatically constructed when you add the trigger and the asset instance (assuming they are grouped together as a library object/"prefab" and your library command has behavior to do so).
Optionally, your trigger could raise an event on a well known node (such as a system node) to which other objects may listen. The issue here would be, how do those objects know to subscribe? You would need to either sent a remote call during login to an area containing the list of objects that need to listen for these types of events, or replicate that information in either individual nodes or a single node containing the information about all of them.
Note, its generally not recommended that you attempt to replicate classes on asset instances directly as asset instances are introduced via a different code path (than that of replication traffic) on the client and you could encounter race conditions (in particular, if you use the seamless world capabilities of the engine). Which is the reason I suggest alternate nodes if you are using replication to introduce the information to the client.
See Also: Triggers
What is a replication group?
A replication group represents a conceptual grouping of nodes that are introduced together to a destination.
How do I create my own node with an initial character "$"?
All prototypes you create can technically also be used to create a system node. The $ syntax tells the engine to look for a prototype with the name following the $, and if it finds one to create a singleton instance (non-persistent) in the local GOM. Thereafter, all references to the system node in that particular GOM will reference the singleton the engine created for you.
What is the difference between a system node and a prototype?
There is a subtle difference...as well as a number of features specific to a system node.
A system node is a singleton created from a prototype (i.e. starts off with its values set as the same as those of the prototype) to which you have convenient access in HSL via the $NAME syntax and which may be modified in a production game. System nodes also form the primary entrance points from C++ into HSL allowing a licensee to extend or override the engine's normal behaviors. Many script systems use system nodes even when they do not receive C++ events because of the extremely convenient syntax providing object oriented access via method calls and member fields to a "system".
Prototypes are stored in the DOM (data object model) and while you may change them during development, it is not recommended that you do so in a production game due to DOM Coordination. DOM Coordination is a step in which all area servers come to a steady state (no scripts running) and apply a change in one atomic operation, should any area server fail to make the change it is reverted on all area servers. In practical terms this is never a problem on a development world running 10s to 100s of area servers, but when you are talking about a production game supporting thousands or tens of thousands of players on a large number of area servers it becomes more of an issue and can take a perceptible amount of time to get all area servers synchronized (during which the game servers would be unresponsive to player commands as no scripts are running).
How do I tie the destruction of a node to the closing of the window?
You can either destroy the node as a part of the submit set using DestroyNode(), or you can simply use the association engine to handle it for you by adding a "base_hard_association" for the edit node (target) to the gui control (source).
AddAssociation( myGUI, "base_hard_association", theEditNode )
How can I ensure that instances I create are destroyed with their parent?
HeroScript does not have Constructors or Destructors as in some languages Constructors/Destructors. However, there are several different things you can do to ensure that the instances you create are destroyed with their parent.
- Use a hard association Associations. Hard associations represent "ownership" as in a parent/child relationship and when you delete the parent the engine enforces the destruction of the children.
- AddAssociation( parent, "base_hard_association", child )
- Instead of using DestroyNode() directly, call a _delete() method on your object that functions as a wrapper for DestroyNode() that performs any cleanup you want and then destroys itself.
- Assuming you are destroying the instance using $EDIT's _RemoveInstance Method (HeroBlade's edits use this), a callback is made to the class methods scripts for the instance at the shared function _OnInstanceRemoved() just prior to node destruction.
- shared function _OnInstanceRemoved( instance as NodeRef of Class Instance )
If you are using this with an instance of an area asset with a glommed class, simple cleanup can be done using a hard association and more complex cleanup can be done by implementing the shared function OnInstanceRemoved() in the class method scripts glommed onto the instance.
When I destroy a parent node does its child nodes get deleted as well?
Deletion of a node will cause a recursive delete down the hard associated nodes if they are persistent or not.
Expanding upon that:
Assuming NodeA is the source, when you delete NodeA it would take NodeB with it (no leak). If you delete NodeB (the target of the association) then NodeA remains in memory (potentially a leak if you no longer need NodeA.
Basically think of associations as a tree-like structure, deleting the trunk (NodeA) takes the entire tree. Deleting a leaf, only removes the leaf. The pseudo diagram below uses / | \ to indicate hard associations with the source being above the target.
A (source) /|\ B C D (target) / E (target of association B as source)
Delete A - entire tree of nodes is deleted
Delete B - B and E are deleted
Delete C - only C is deleted
In what order is rotation applied?
yaw (heading), pitch, roll. Anything else makes no sense. Imagine an airplane. You have to point it in the direction you are heading, then lift or lower the nose and finally roll it around it's forward pointing axis.
Rotates around it's Y (up), then it's X (right) and finally around it's Z (forward).