Single/Multiplayer, Touch & Vive blueprint only Template

Ok so here’s my way of doing things. Of course other could decide to do otherwise

First of all, terminology. Let’s say there’s 3 players, each logged in Oculus Home under their respective ID.
First in MainMenu when connecting to Oculus, the Oculus ID is retrieved through my function OculusLogin and assigned to AvatarMaster, through the GameInfoInstance. This is true for all players.
First to login is the Authority, locally-controlled player 0 (listen-server). I call him AUT_LOC_CTRL0.
Then, another player connects. When he arrives on MultiMap, that’s where interesting things happens. This player is spawned on the server as Authority, Not Locally-controlled Player 1 (AUT_NOTLOC_CTRL1), and then replicated as Remote, Locally-controlled Player 1 (REM_LOC_CTRL1). At the same time, Player 0, as per replication dictates it, appears to REM_LOC_CTRL1 as Remote, Not Locally-Controlled Player 0 (REM_NOTLOC_CTRL0).
So if 2 players are connected (1 listen-server, the other one as client), 4 copies of AvatarMaster are live in the Unreal world: 2 on the server, and 2 on the client.
Every other client is connecting the same way; however special attention must be given to interactions/replications between clients (REM_LOC_CTRL1 <-> REM_LOC_CTRL2). Anyway.
How I made Avatar work / How I would make it work multiplayer

So everytime a client connects, he goes through all other clients and make them spawns an AvatarRemote together with his OculusID, so tada the AvatarRemote spawned by every other clients has the appearance of the original client connecting.
Furthermore, as the client connects, he make also the server spawns a copy of RemoteAvatar, for AUT_LOC_CTRL0 to see. Finally, the authority, not locally-controlled copy of the freshly connected client will also spawn a copy of AvatarRemote to previous clients in case there’s more than 2 players.
The problem with previous template (3.1.1 with Avatars, Unreal 4.16) was that multiplayer Avatars were all controlled by whatever the locally-controlled player that was. Bad.

So let’s dive in code.

**ProteusLocal.cpp: **Enable Record Packets
Actual code:
void AProteusLocal::BeginPlay()
{
Super::BeginPlay();
AProteusLocal::AdditionalSetup();
}
void AProteusLocal:: I’ll append this function to RegisterRemoteTalker:
{
AProteusLocal::EnableRecordPackets();
}
void AProteusLocal::EnableRecordPackets()
{
if (!AvatarComponent)
return;
AvatarComponent->StartPacketRecording();

   PacketSettings.AccumulatedTime = 0.f;
   PacketSettings.RecordingFrames = !PacketSettings.RecordingFrames;

}
OvrAvatar.cpp: Start Packet Recording
Actual code:
void UOvrAvatar::StartPacketRecording()
{
if (!Avatar)
return;

   ovrAvatarPacket_BeginRecording(Avatar);

}
Begin Recording leads to an Oculus header file library function with an opaque pointer as ovrAvatarPacket, so we’ll take it from there.
Then, the packets are updated in ProteusLocal.cpp in the tick, as UpdatePacketRecording.
Actual code:
void AProteusLocal::UpdatePacketRecording(float DeltaTime)
{
if (!AvatarComponent)
return;

   if (!PacketSettings.Initialized)
   {
          AvatarComponent-&gt;StartPacketRecording();
          PacketSettings.AccumulatedTime = 0.f;
          PacketSettings.RecordingFrames = true;
          PacketSettings.Initialized = true;
   }

   if (PacketSettings.RecordingFrames)
   {
          PacketSettings.AccumulatedTime += DeltaTime;

          if (PacketSettings.AccumulatedTime &gt;= PacketSettings.UpdateRate)
          {
                 PacketSettings.AccumulatedTime = 0.f;
                 FOvrAvatarManager::Get().QueueAvatarPacket(AvatarComponent-&gt;EndPacketRecording());
                 AvatarComponent-&gt;StartPacketRecording();
          }
   }

}
This is where it is becoming interesting. Update Packet Recording calls QueueA Avatar Packet in the** OVRAvatarManager**, where packets are serialized and queued:
Code:
// Setting a max in case there is no consumer and recording turned on.
static const uint32_t SANITY_SIZE = 500;
void FOvrAvatarManager::QueueAvatarPacket(ovrAvatarPacket* packet)
{
if (packet == nullptr)
{
return;
}

   SerializedPacketBuffer Buffer;

   for (auto& QueuePair : AvatarPacketQueues)
   {
          auto Queue = QueuePair.Value;
          if (Queue-&gt;PacketQueueSize &gt;= SANITY_SIZE)
          {
                 UE_LOG(LogAvatars, Warning, TEXT("[Avatars] Unexpectedly large amount of packets recorded, losing data"));
                 Queue-&gt;PacketQueue.Dequeue(Buffer);
                 Queue-&gt;PacketQueueSize--;
                 delete] Buffer.Buffer;
          }

          Queue-&gt;PacketQueueSize++;

          Buffer.Size = ovrAvatarPacket_GetSize(packet);
          Buffer.Buffer = new uint8_t[Buffer.Size];
          ovrAvatarPacket_Write(packet, Buffer.Size, Buffer.Buffer);
          Queue-&gt;PacketQueue.Enqueue(Buffer);
   }

   ovrAvatarPacket_Free(packet);

}

MY IDEA

Serialized packet buffers are now converted into AvatarPacketQueues
AvatarPacketsQueues are in fact a TMap<FString, AvatarPacketQueue*> AvatarPacketQueues, and AvatarPacketQueue are a struct AvatarPacketQueue { TQueue<SerializedPacketBuffer> PacketQueue; uint32_t PacketQueueSize = 0;};

We find later the header file of ProteusRemote that the FString is an arbitrary identifier, there it is found to be GetName() but it could be more useful to be the Oculus ID.

SO
What I would do is Local gets AvatarPacketQueues (as a TMap) and exports it every tick to a class reachable to all clients. The string key in the TMap here would be the Oculus ID. Probably the GameInfo Instance?

Then, as Remote is spawned it register as a remote Avatar under its Oculus ID:

void FOvrAvatarManager::RegisterRemoteAvatar(const FString& key)
{
check(!AvatarPacketQueues.Find(key));

   AvatarPacketQueue* NewQueue = new AvatarPacketQueue();
   AvatarPacketQueues.Add(key, NewQueue);

}

Then it reaches every tick GameInfo instance via an interface, finds its key in the TMap, and retrieve its associated AvatarPacketQueue.
Then AvatarPAcketQueue is retranscribed to avatarpacket via the AvatarModuleManager RequestAvatarPacket:

ovrAvatarPacket* FOvrAvatarManager::RequestAvatarPacket(const FString& key)
{
ovrAvatarPacket* ReturnPacket = nullptr;

   if (auto Queue = AvatarPacketQueues.Find(key))
   {
          SerializedPacketBuffer Buffer;

          if ((*Queue)-&gt;PacketQueue.Peek(Buffer))
          {
                 (*Queue)-&gt;PacketQueueSize--;
                 (*Queue)-&gt;PacketQueue.Dequeue(Buffer);
                 ReturnPacket = ovrAvatarPacket_Read(Buffer.Size, Buffer.Buffer);
                 delete] Buffer.Buffer;
          }
   }

   return ReturnPacket;

}

Would it work? Would have to try it!

I’ll release this his week so we’ll be able to solve this!