How does the client know that it is ready so that it can activate a character?
Or more specifically, how can the client tell when all assets have been loaded? (from the context of character selection)
Typically, on the client, $BASECLIENT's _Area_Load should be called after the area is completely loaded and ready. If you know you have additional assets you need to create locally, you can add them during the _Area_Preload which will result in the client waiting the additional time while those resources are loaded before telling the server it is "ready".
With character outfits, you cannot send the information from the server early enough for you to add them during the preload step so that does not help us for this particular need (the earliest you can communicate with the client from the server is in the server's _ClientReady method).
The visualizations you create of characters by sending their outfits to the client are local visualizations only, the server does not know it needs to wait for them to be ready.
The client does not notify the server that it is "ready" until all of the assets have loaded, at which time it should have all of the character data for the characters it has been told to create by virtue of C++ reflecting them to the client. However, when you are sending outfit handles to the client for character selection you would need to tell the client to create those local characters during the server's _ClientReady method to send the outfits to the client and not call PlayerLoadFinished() until all of the outfits you sent to the client had been successfully created as characters.
The $CHARACTERSYSTEM _OnCharacterCreate method is called when characters have been created and start rendering. Your system could index all of the outfits it has been told to create and then hook into character system to check each one off as it is created. When all of them are created, you probably would hide those you do not want to have shown and leave just the "last selected" character visible then call the server to do the external function all PlayerLoadFinished().
So in outline form:
- (server) Create a Game-Specific HE_PreClientReady, or HE_PostClientReady override for $ACCOUNT
- Peform a remote call to the client to tell it to expect X character outfits
- Use the external function to send the character outfits to the client
- (client) Create a Game-Specific HE_OnCharacterCreate and HE_GotCharacterOutfit overrides for $CHARACTERSYSTEM
- When you get the OnCharacterCreate for the characters you expect, you mark each one as ready
- When all outfits have been created, toggle the visibility of those you do not want displayed initially to hidden.
- Remote call the server and perform the PlayerLoadFinished() function call. The server will wait up to one minute for this to be done.
In setting up the character selection scene, I currently activate the GUI and fade the world in on the OnEnteredArea event. However, this is way too early, as much of the world assets are still loading and you see them pop in. Is there a callback I can use to determine that all assets have been loaded and are ready to view? I see the IsCharacterReady() which I assume works on individual characters. Does the client HSL receive some sort of callback when ALL outstanding asset loads have completed? Does it receive a callback when each character becomes ready, or do I have to poll them all?
A: Assuming you mean on the server, the $ACCOUNT node has a _CharacterActivated method with a game-specific override HE_PostCharacterActivated that occurs after the client reports that it has loaded everything and activated the character. The event just prior to that is the _ClientReady event, as shown below, is the earliest you can reliably communicate with the client as it is has indicated it has loaded the DOM/GOM and is ready to receive communication from the server.
Is there a rationale for why the client player HBnode gets created with the id of the player account instead of that of the player character, such that pc’s and npc’s would be consistent?
While there is an argument that could be made for having the HBNode be assigned the character's GUID, one could also be made for assigning it the GUID of the current _CharacterAppearance node for a given character (which is the node that actually has the ghost class that handles visualization for the client).
On the servers, accounts are the fundamental root node representing a customer. This facilitates HeroEngine's ability to dynamically assign control ("possessing") of another character because the server does not really care which character is involved, only the account. Likewise, the visible representation of a character may easily be switched between the 1...n _CharacterAppearance nodes associated to the controlled character. The decision to use the _PlayerAccount GUID was an arbitrary one, choosing the one ID that was not subject to change during a session.
Do you have any guidelines for making use of the automatic client HBNodes? For example, would it be advisable to glom classes onto these HBnodes and populate them with additional fields containing data that you want to associate with characters client-side?
Our usage of the automatically instantiated HBNodes (creatures, characters, assets, etc.) is exactly as you describe, we GLOM on additional classes as dictated by remote calls from the server which pass any information needed.
This topic came up for discussion with the engineers. We mentioned it would be nice for this to be handled at the ghost class. This would minimize the amount of code required in HSL to give a client-side node behaviors which were appropriate to its "type", along with potentially some default values. Our proposed solution is to add a couple fields marked +reflect so they communicate state changes to the underlying ghost class for the node (and then add the C++ code in the ghost classes to handle these new fields). The first field would be the name of a class that would be automatically GLOMmed when the HBNode is instantiated. The second field would be an arbitrary data string. Following the GLOM of the specified class, a method call would be made into that class passing in the arbitrary data string, allowing the class to GLOM additional classes, set fields to their values and whatever else you might pack in a string that the class knows how to parse.