With the Sapphire release, we made several improvements to the way seamless transitions are handled. One such change is a more efficient method of handling situations in which an area is seamlessly linked to both the source and destination areas of a seamless transition. To facilitate this change, we moved the subscription/unsubscription of a player to edit queues and spatial awareness systems to a set of new methods in _AccountClassMethods. You'll find the new code in _AccountClassMethods::_SubscribeAccountToSpatialAwarenessForArea.
In order to help developers make better use of the new methods, we decided to expose four new overrides that can be used to extend or override the default functionality. These overrides did not go out with the initial Sapphire release, so I've provided instructions on how to add them manually (we will of course push these changes out to everyone in the next package release).
Instructions:
1. In _MethodCallbacksClassMethods (server), add the following definitions to the tail of the file:
method HE_SubscribeClientToAreaEditQueues( account_id as ID ) as Boolean
return false
.
method HE_UnsubscribeClientFromAreaEditQueues( account_id as ID ) as Boolean
return false
.
method HE_SubscribeAccountToSpatialAwarenessForArea(accountID as ID, areaID as ID, instanceID as ID) as Boolean
return false
.
method HE_UnsubscribeAccountFromSpatialAwarenessForArea(accountID as ID, areaID as ID, instanceID as ID) as Boolean
return false
.
2. In _AccountClassMethods, replace these four methods (located roughly at line 248 and ending roughly at line 379) with the following code (note: the only changes to these methods are the additions of calls to the HE_ overrides):
method _SubscribeClientToAreaEditQueues( account_id as ID )
handled as Boolean
if hasMethod( me, "HE_SubscribeClientToAreaEditQueues" )
handled = me.HE_SubscribeClientToAreaEditQueues(account_ID)
.
if not handled
connection as NodeRef of Class _PlayerConnection = GetPlayerConnectionNode(account_id)
areaID as ID = GetAreaNumber()
instanceID as ID = GetInstanceNumber()
//Deliver edit command queue subscription event for current area
connection._OnPlayerSubscribeToEditCommandQueue(areaID, instanceID)
client_reference_token as String = GetPlayerReferenceToken( account_id )
area_node as NodeRef of Class AreaNode = $AREA._GetAreaNode()
currentSubscriptions as LookupList indexed by ID of List of ID
foreach linked_area in area_node._areaSeamlessLinks
add back instanceID to currentSubscriptions[linked_area]
.
oldSubscriptions as LookupList indexed by ID of List of ID
//add the area we just came from to the old list, provided the value is initialized to something other than the world server
if connection._playerConnectionTraveledFrom.AreaID != 0
add back connection._playerConnectionTraveledFrom.AreaInstanceNumber to oldSubscriptions[connection._playerConnectionTraveledFrom.AreaID]
.
//add each area instance to which our previous area was seamlessly connected to the old list
foreach oldSeamlessLink in connection._playerConnectionTraveledSeamlessLinks
add back oldSeamlessLink.AreaInstanceNumber to oldSubscriptions[oldSeamlessLink.AreaID]
.
//For each potential new subscription, subscribe the client if it wasn't already subscribed
foreach potentialSubscription in currentSubscriptions
if not (connection._playerConnectionTraveledSeamlessly and oldSubscriptions has potentialSubscription)
$EDIT._SubscribeClientToEditQueueByToken( account_id, potentialSubscription, instanceID, client_reference_token )
connection._OnPlayerSubscribeToEditCommandQueue(potentialSubscription, instanceID)
$ACCOUNT._SubscribeAccountToSpatialAwarenessForArea(account_id, potentialSubscription, instanceID)
.
.
//Add current area to prevent its removal as stale
add back instanceID to currentSubscriptions[areaID]
//subscribe to the current area's sas since we haven't done that yet
$ACCOUNT._SubscribeAccountToSpatialAwarenessForArea(account_id, areaID, instanceID)
//For each old subscription, unsubscribe the client if it isn't still important
//allSubscriptions as LookupList indexed by ID of List of ID = connection._GetPlayerSubscribedEditCommandQueues()
staleSubscriptions as LookupList indexed by ID of List of ID
if connection._playerConnectionTraveledSeamlessly
foreach potentialUnsubscription in oldSubscriptions
if not (currentSubscriptions has potentialUnsubscription)
add back instanceID to staleSubscriptions[potentialUnsubscription]
.
.
.
foreach staleSubscription in staleSubscriptions
$ACCOUNT._UnsubscribeAccountFromSpatialAwarenessForArea(account_id, staleSubscription, instanceID)
$EDIT._UnsubscribeClientFromEditQueue( connection._accountID, staleSubscription, instanceID)
connection._OnPlayerUnsubscribeFromEditCommandQueue(staleSubscription, instanceID)
.
.
.
method _UnsubscribeClientFromAreaEditQueues( account_id as ID )
handled as Boolean
if hasMethod( me, "HE_UnsubscribeClientFromAreaEditQueues" )
handled = me.HE_UnsubscribeClientFromAreaEditQueues(account_id)
.
if not handled
connection as NodeRef of Class _PlayerConnection = GetPlayerConnectionNode(account_id)
areaID as ID = GetAreaNumber()
instanceID as ID = GetInstanceNumber()
$EDIT._UnsubscribeClientFromEditQueue( account_id, areaID, instanceID)
connection._OnPlayerUnsubscribeFromEditCommandQueue(areaID, instanceID)
$ACCOUNT._UnsubscribeAccountFromSpatialAwarenessForArea(account_id, areaID, instanceID)
area_node as NodeRef of Class AreaNode = $AREA._GetAreaNode()
foreach linked_area in area_node._areaSeamlessLinks
$EDIT._UnsubscribeClientFromEditQueue( account_id, linked_area, instanceID)
connection._OnPlayerUnsubscribeFromEditCommandQueue(linked_area, instanceID)
.
.
.
remote method _SubscribeAccountToSpatialAwarenessForArea(accountID as ID, areaID as ID, instanceID as ID)
handled as Boolean
if hasMethod( me, "HE_SubscribeAccountToSpatialAwarenessForArea" )
handled = me.HE_SubscribeAccountToSpatialAwarenessForArea(accountID, areaID, instanceID)
.
if not handled
thisAreaID as ID = GetAreaNumber()
thisInstanceID as ID = GetInstanceNumber()
if thisAreaID != areaID or thisInstanceID != instanceID
call area areaID instance instanceID $ACCOUNT._SubscribeAccountToSpatialAwarenessForArea(accountID, areaID, instanceID)
return
.
account as NodeRef of Class _PlayerAccount = accountID
e as Class _SAS_EntityInformation
// Use the range of 70 meters as the awareness range
e._SAS_EI_awareness = 7.0
if account != None
e._SAS_EI_position = account.GetPosition()
.
add back accountID to e._SAS_EI_event_receiving_nodes
$SPATIALAWARENESS_AREA._SAS_AddEntity(accountID, e)
subjects as List of ID
subject as NodeRef of Class _PlayerAccount
$SPATIALAWARENESS_AREA._SAS_QueryByEntity(accountID, subjects)
if account != None
foreach subj in subjects
account._SO_Entered($SPATIALAWARENESS_AREA, account, subj)
subject = subj
if subject != None
subject._SO_Entered($SPATIALAWARENESS_AREA, subj, account)
.
.
.
.
.
remote method _UnsubscribeAccountFromSpatialAwarenessForArea(accountID as ID, areaID as ID, instanceID as ID)
handled as Boolean
if hasMethod( me, "HE_UnsubscribeAccountFromSpatialAwarenessForArea" )
handled = me.HE_UnsubscribeAccountFromSpatialAwarenessForArea(accountID, areaID, instanceID)
.
if not handled
thisAreaID as ID = GetAreaNumber()
thisInstanceID as ID = GetInstanceNumber()
if thisAreaID != areaID or thisInstanceID != instanceID
call area areaID instance instanceID $ACCOUNT._UnsubscribeAccountFromSpatialAwarenessForArea(accountID, areaID, instanceID)
return
.
account as NodeRef of Class _PlayerAccount = accountID
if account != None
// query what I observe
subjects as List of ID
subject as NodeRef of Class _PlayerAccount
$SPATIALAWARENESS_AREA._SAS_QueryByEntity(account, subjects)
foreach subj in subjects
account._SO_departed($SPATIALAWARENESS_AREA, account, subj)
subject = subj
if subject != None
subject._SO_departed($SPATIALAWARENESS_AREA, subj, account)
.
.
.
$SPATIALAWARENESS_AREA._SAS_RemoveEntity(accountID)
.
.
3. Follow the instructions on
Adapting Clean Engine to add a game-specific override to the $ACCOUNT system node and add your own custom code (e.g. your modification of the spatial awareness range) to the proper override (making sure to 'return true' from the method in order to prevent the original code from negating your override).
Now - during a seamless transition - your new override's code will be executed! You can repeat this procedure for any of the other overrides in order to custom-tailor the subscription/unsubscription process.
This change will allow you to leverage these overrides before they become an official part of the product; once officially deployed to the cloud, these changes will then become available to all developers in HeroCloud worlds (no need to undo your manual changes, as this will be done automatically for you).
Enjoy!