Replicate to Only Allowed Players?

Hey so I am trying to figure out replication on a slightly deeper level, which is to only replicate to players who “have access” to an actor. The access to this actor is determined if a player interacts with the object or not.

My Scenario:
I have an inventory component which I can attach to things such as a player, storage box, etc… If a player interacts with a storage box then they should receive all information about that current inventory as they open it as well as any updates that are made to that specific inventory as long as they have “access” (for example, if another player is viewing the storage container and add/removes an item).

My current implementation:
I am replicating the inventory items to all players using a plain DOREPLIFETIME, which results in replicating to all clients. I am also overriding ReplicateSubobjects to only update items that actually change.

I have experimented with adding a condition in ReplicateSubobjects which basically blocks replication based on conditions if the current channel’s unique net id is found in the storage boxes let of allowed players. I did run into an issue where if say player 1 puts an item in the box and then player 2 goes and accesses the box, they would not see the item in the box, but would see it after another item was added.

I feel like I am on the right track to doing this, however I am curious if there is an easier way of doing this? Ideally I would like the player’s inventory to only replicate to their owning client and given the ability to give additional access to other players if they interact with a storage box.

Hi Rob,

Not sure if this is exactly the same but I’ve just implemented what I think is similar for my inventory system.

So I have an inventory component I can attach to any actor and one of the modes I have is shared container. In that mode it can have items taken out and put in by multiple players which I think is what you are doing as well. The component just replicates a simple TArray of structs at it’s heart, I might do something more clever with that in the future for replication but it’s fine for now.

In my scenario the player has an inventory component as well and you can move stuff between inventory components as required.

I approached it like this.

I’ve got a simple bool replicating on the container actor that says if it’s locked to a player or not. If it’s not locked to a player whoever interacts with it I set the owner of the actor to be the player interacting on the server. I have an event listening for when the owner changes on the client, once that happens I can then make RPC calls as the owner from the interacting pawn actor against the inventory component on the static actor container, do whatever with the Inventory component and it updates on the server and replicates etc. then once I’m done it’s unlocked and a different player can use and see the changes to the inventory component on the static actor.

This is work in progress but something like this from the actor wanting to interact, I’ve created an interact interface so I just see if there is an overlapping actor that has an interface.

If the goal is multiple players interacting with the same inventory component at the same time I’d have to approach it differently and lock individual inventory items rather than the actor object which I’ve left the solution flexible enough to cope with but haven’t implemented as don’t need it at the moment.




void AGyCharacterBase::Interact()
{
//on client has been instigated by player
//TODO::Might move this whole check to server rather than checking client first
//Container interact has more server checks as does inventory component
TArray<AActor*> OverlappingActors;

GetOverlappingActors(OverlappingActors, AActor::StaticClass());

for (AActor* a : OverlappingActors) { if (IsValid(a))
{
  [INDENT=2]InteractInterface = Cast<IGyInteractInterface>(a);

if (InteractInterface != nullptr)
{[/INDENT]
  [INDENT=3]FLockResponse lockResponse;
if (InteractInterface->LockInteractionToPlayer(this, lockResponse) == true)
{[/INDENT]
  [INDENT=4]//all good we own this start interaction we either own it or it doesn't need to be owned to work
StartInteraction();[/INDENT]
  [INDENT=3]}
else if (lockResponse.LockStatus == ELockToPlayer::LockAvailable)
{[/INDENT]
  [INDENT=4]//it's free but need to get ownership first
if (GetLocalRole() == ROLE_Authority)
{[/INDENT]
  [INDENT=5]//I'm running as server host or standalone just set it to match me
SetRemoteOwner(a);
StartInteraction();[/INDENT]
  [INDENT=4]}
else
{[/INDENT]
  [INDENT=5]//I'm running as client ask server to set it and Start Interaction once it's replicated
InteractInterface->GetOwnerChangedDelegate().AddUObject(this, &AGyCharacterBase::OnInteractReady);
Server_SetRemoteOwner(a);[/INDENT]
  [INDENT=4]}[/INDENT]
  [INDENT=3]}
else
{[/INDENT]
  [INDENT=4]UE_LOG(GyLog, Warning, TEXT("AGyCharacterBase::Interact %s %s"), *lockResponse.Message.ToString(), *GetName());
//it's locked display lock message;[/INDENT]
  [INDENT=3]}
break;[/INDENT]
  [INDENT=3]}[/INDENT]
  [INDENT=2]}[/INDENT]
  }
 
 }


Hey man,

Yeah this is sort of what I am looking for. Basically want to set the remote owner to the person who is accessing the inventory but my end result is wanting multiple players to interact with the same storage containers so I may have to take a different approach to this.

What I am thinking of doing is having an inventory manager component which is on the player controller. You will still have an inventory component which will host all your items and will be on every entity that has an inventory, however none of that data will be replicated. Instead it will be copied to the inventory manager and replication happens on those copied objects and replaced on the corresponding inventories instead. Not certain that is what I am going to do but I feel like this is another good option without needing to custom replicate things like I was trying to do in the ReplicateSubobjects method.

Thanks so much for your feedback though I did not know you could set the remote owner of an object so that will be very useful in the future.

That’s an interesting approach I didn’t think of that, will keep it in mind as I go forward as well.

I have approached it differently, I’ve made sure each inventory object is totally independent, they can even spawn their own custom inventory windows etc. for non pawns I make sure I get to be the owner first then use the relevant player controller to spawn the custom inventory windows from the inventory component and interact with the inventory component.

The inventory items are just references to primary asset id’s + a few additional inventory slot specific attributes like quantity, what’s allowed in the slot etc.

Then I’ve used the SaveGame tag to flag what I want to persist and then on change will have a separate process that serializes that behind the scenes on whatever frequency works and keeps a snap shot for in game with no direct player owner and player owned stuff (I’ve abstracted the storage part so it could be file system, database or whatever with a queue etc. it doesn’t matter) ) for critical transactions I’ll set it up to force the save something like that. Then if the game server gets shut down I’ll have latest snapshot and if it shuts down cleanly I can run that as I log everyone off.

I’m probably massively over thinking this but seeing as I’m starting from scratch might as well try and bake it in!

When a player has put something back into the container. Just make that a client to server call then have the server side set the var that needs replicated and send that var for that specifc item to all the other clients that are interacting with that container. Then they should see it and be able to interact with the new item that was just put in. In the function OnRep_bMovePieceIn(); send all the interacting container clients the replicated var so they can see it.

EDIT: Something like this should work



Example code, it should work.

/*
PutInPiece() CLIENT puts in a PIECE Start functon from button or what ever trigers it
*/
void AYourClass::PutInPiece()
{
    ENetMode NetMode = GetNetMode();

    //if your are in standalone or listen server you will run this and ingnore client call as you are the server.
    PutThePieceIn();//If your the client you will run this and execute the server functon if it valid to do so. Then the server will do what client asked.


    if (NetMode == NM_Client)
    {
       ServerDoSomething();
    }
}

void AMyActor::ServerDoSomething_Implementation()
{
     PutThePieceIn();
}

bool AMyActor::ServerDoSomething_Validate()
{
    return true;
}

/*
SetThePiece(); end function to actually put in the piece
*/
void AYourClass::PutThePieceIn()
{
     //server side replicate this to all interacting clients but yourself this will be the var you set to all interacting clients about new item put into the container.
     bMovePieceIn = !bMovePieceIn;//in its replication function OnRep_bMovePieceIn(); have that call a function to set the actual items replicated var so each          //interacting client can see it. That should make it useable to them all.

      //rest of your code to actually put in your piece.
}


Have the class not replicate be default, so the server doesn’t feed everyone (relevancy). Or set its relevancy distance to a small value.
On interaction request an update from the server. On add/remove have the server make the updates and send a response back.

Thanks so much for the feedback guys. I think I am going to take the approach on not feeding anyone the inventory and only feeding them what is in the inventory on request. I already have some ideas around doing this, which is to utilize either the player state or the player controller (create an inventory manager component class that handles everything inventory-related and the server will handle populating what data needs to be replicated to only that player client, whether that be their own personal inventory or a storage container they are looking at, etc…