Jump to content

Introduction to Replicas


Recommended Posts

I'm somewhat new to modding DST, and I haven't dealt much with host-client communications yet since I haven't needed to, but I do now so I've been looking at examples and searching through the forums but the way components and their replicas should be made is still not clear to me.

I examined Kzisor's leveler component and its companion exp widget, as well as some standard game components and random things I found in the mods I have installed, but still.

Some use classifieds, some use netvars, some reference the replica in the main component, some don't...I could really use a basic rundown of when you use replicable components as opposed to a regular component added in common_postinit(); when you should use player_classified (and what it even is to begin with); when to use netvars (are the instead of the classified thing?) - and if possible, seeing an example of a simple "Hello world" replicable component would be really great.

Thanks in advance. =)

P.S.
If you're interested in the functional context here, I'm trying to create a "transformer" component to manage all character transformations in my mod.
It's basically a state machine with forms and triggers that activate the transitions between them.
I'd like to give it an AddTransformation() function that lets you define the transitions as well as the trigger, and, for example, I'd like key press to be one of the options.
I really want to encapsulate the entirety of the functionality into the component and its replica.
Ideally, the user would simply write something such as transformer:AddTransformation("justme", "superme", transformer.Triggers.OnKey(SOME_KEY)) where transformer is the component or its replica and it will simply work.
For that end I made registered an RPC call for all transformations that passes along some transformation ID.
Now I need to have the code that performs the transformation to be on the server and the code that listens for a key and invokes the RPC to be on the client, but I don't know what to put where, and how to raise an event on the client when the transformation is done.
Thus I come seeking the wisdom of the forum elders.

Link to comment
Share on other sites

  • Developer

From my experience with the code, this is how it should work:
the purpose of a replica is universal API, the idea, is a replica will redirect to the component when on the server, and will generally run some form of RPC and/or visual manipulation on the client, WARNING: its possible for a player to exist on mastersim, meaning you need to properly handle that case also; so you use a replica when you need some form of universal access regardless of client/server, OR if you need netvars.
now, onto using a classified, vs not using a classified:
in general, there is two reasons to use a classified, the first is you have a component, which needs netvars to properly work for the player, but will exist on other entities as well, then you can store the netvars(via a PrefabPostInit) inside player_classified(for one or two netvars), or a specialized classified(lots of netvars/some other special cases.), and you only need to spawn the classified entity for players. the second, is to keep access restricted to a single player(I'm looking at you containers).

lastly, two important functions relating to classifieds:
inst.entity:SetInLimbo(true/false), this function when run, will remove the entity from existence on all connected clients(if set to true, false does the inversion of that).

inst.Network:SetClassifiedTarget(entity), what this function does, depends on whether or not the inst is inlimbo,
if inst is inlimbo it will do the following:
if the entity passed is a specific player, it will cause that classified entity to spawn for that specific client only, otherwise it will remove it from any existing player that it had as a classified target.
if inst isn't inlimbo it will do the following:
if the classified target is a specific player, it will exist only on mastersim and that specific players client, if set to nil, it exists everywhere, if set to any other entity, it mimics the behavior of inlimbo(only existing on mastersim), but without some of the other inlimbo effects.

if you want to see examples of all I'm talking about in action, look at locomotor.lua, container.lua container_replica.lua and container_classified.lua, and also inventoryitem.lua inventoryitem_replica.lua and inventoryitem_classified.lua.

Link to comment
Share on other sites

First of all, thank you very much, Zarklord; what you wrote is really helpful. =)

Ok, so I think I understand the concept of replicas (though I have a question pertaining to them which I'll write in a moment), and I think I understand more about classifieds now, but I didn't understand part of your explanation.

I searched for "SetInLimbo" in the DST code (and my random collection of mods) and I only found it in entityscript.lua, in the functions RemoveFromScene() and ReturnToScene(). So regarding that:

  1. Is there ever a reason to use it directly?
  2. I looked for uses of RemoveFromScene() in the game code and found several, one of which is in inventoryitem.lua, inside OnPutInInventory().
    If I understand what you said correctly, this means that when an item is picked up, its entity is removed from all clients.
    However, I seem to remember reading at some point that all the items in all the containers everywhere are stored in memory for all the clients at all times.
    How do these two fit together - or, alternatively, is my memory and/or the information inaccurate?

Now, about this:

2 hours ago, Zarklord said:

if the entity passed is a specific player, it will cause that classified entity to spawn for that specific client only, otherwise it will remove it from any existing player that it had as a classified target.

I don't really get it.
Let's try using concrete values, if that's OK.
Say inst is some carrot and entity is Woodie.
So if I invoke inst.Network:SetClassifiedTarget(entity), it will cause that entity, Woodie, to spawn only for the player playing that Woodie?
I don't understand the meaning of that sentence.
Does it need to be run before Woodie is spawned?
If not, what happens if it's spawned already? Would Woodie disappear from everybody's clients except his own?

I have similar comprehension issues with the out-of-limbo case, but clearing the above up will probably clear those up too.

Now for a more specific question:
How do I send information from the server to a specific client?
Let's assume I have that client's player entity and/or I'm inside the scope of an RPC handler that was invoked by that client.

I have a clear idea of what data I need to have where (server / client, who needs it, who doesn't) and what messages I want to send, when and between whom.
I think I can implement all of the above with replicas, but if I set a netvar for a replica, won't it be sent to all the clients?
In some cases that's just wasted network data since only one player will do something other than ignore it.

Once again, I truly appreciate your assistance here, so thank you very much. :)

Edited by Clopen
Link to comment
Share on other sites

Actually, one more question...

common_postinit() is called for an entity on both the server and client, right?
And we basically look at TheWorld.ismastersim in order to determine whether we're on the client or the server - but how do we know if the player is remote or not?

I have keyboard events I want to register to, and these need to be on the client only, but if someone is playing in a world without caves and they're the host then the value of TheWorld.ismastersim would still be true, no?
So how do I know whether to register the key handler or not?

Link to comment
Share on other sites

  • Developer
On 5/30/2019 at 1:55 PM, Clopen said:

First of all, thank you very much, Zarklord; what you wrote is really helpful. =)

Ok, so I think I understand the concept of replicas (though I have a question pertaining to them which I'll write in a moment), and I think I understand more about classifieds now, but I didn't understand part of your explanation.

I searched for "SetInLimbo" in the DST code (and my random collection of mods) and I only found it in entityscript.lua, in the functions RemoveFromScene() and ReturnToScene(). So regarding that:

  • Is there ever a reason to use it directly?

Nope, not generally, there is some special cases where you might, but in general no.

On 5/30/2019 at 1:55 PM, Clopen said:
  • I looked for uses of RemoveFromScene() in the game code and found several, one of which is in inventoryitem.lua, inside OnPutInInventory().
    If I understand what you said correctly, this means that when an item is picked up, its entity is removed from all clients.
    However, I seem to remember reading at some point that all the items in all the containers everywhere are stored in memory for all the clients at all times.
    How do these two fit together - or, alternatively, is my memory and/or the information inaccurate?

the mastersim has every thing loaded in some form or another, clients never have items stored in containers loaded unless they have said container open.

On 5/30/2019 at 1:55 PM, Clopen said:

I don't really get it.
Let's try using concrete values, if that's OK.
Say inst is some carrot and entity is Woodie.
So if I invoke inst.Network:SetClassifiedTarget(entity), it will cause that entity, Woodie, to spawn only for the player playing that Woodie?
I don't understand the meaning of that sentence.
Does it need to be run before Woodie is spawned?
If not, what happens if it's spawned already? Would Woodie disappear from everybody's clients except his own?

I have similar comprehension issues with the out-of-limbo case, but clearing the above up will probably clear those up too.

using inst as the carrot, lets handle most of the cases, inst is a carrot, woodie is a player, playing as woodie.
not InLimbo:
inst.Network:SetClassifiedTarget(woodie) (setting classified target to a specific client)
upon running this code, any other remote client that had the carrot loaded will get it despawned on that local client, the woodie client will keep the carrot loaded, no clients besides this woodie can have the carrot loaded.
inst.Network:SetClassifiedTarget(inst) (setting classified target to a random entity)
this will remove the carrot from all remote clients, and no clients can have the carrot loaded.
inst.Network:SetClassifiedTarget(nil) (removing any specific classified target)
this will load/keep the carrot on all clients, and every client can have the carrot loaded.
InLimbo:
inst.Network:SetClassifiedTarget(woodie) (setting classified target to a specific client)
upon running this code, the woodie, will get the carrot spawned on the client, no other clients will have the carrot loaded.
inst.Network:SetClassifiedTarget(inst) (setting classified target to a random entity)
this will remove the carrot from all remote clients, and no clients can have the carrot loaded.
inst.Network:SetClassifiedTarget(nil) (removing any specific classified target)
this will remove the carrot from all remote clients, and no clients can have the carrot loaded.

On 5/30/2019 at 1:55 PM, Clopen said:

Now for a more specific question:
How do I send information from the server to a specific client?
Let's assume I have that client's player entity and/or I'm inside the scope of an RPC handler that was invoked by that client.

I have a clear idea of what data I need to have where (server / client, who needs it, who doesn't) and what messages I want to send, when and between whom.
I think I can implement all of the above with replicas, but if I set a netvar for a replica, won't it be sent to all the clients?
In some cases that's just wasted network data since only one player will do something other than ignore it.

Once again, I truly appreciate your assistance here, so thank you very much. :)

your only option is netvars, if you only need it for players, your best bet is PrefabPostInit-ing the player_classified and adding them there, along with the event pushing/receiving to get the data out/in, you should be able to fairly easily see how to make that work by looking at player_classified.

for instance, doing something inside the RPC like ThePlayer:PushEvent("someevent", {--[[useful data]]}) and properly listening for that event inside the classified would work.

16 hours ago, Clopen said:

Actually, one more question...

common_postinit() is called for an entity on both the server and client, right?
And we basically look at TheWorld.ismastersim in order to determine whether we're on the client or the server - but how do we know if the player is remote or not?

I have keyboard events I want to register to, and these need to be on the client only, but if someone is playing in a world without caves and they're the host then the value of TheWorld.ismastersim would still be true, no?
So how do I know whether to register the key handler or not?

TheNet:IsDedicated() if that returns true, then the server isn't a client also; if it returns false and TheWorld.ismastersim is true, then its client and server at the same time.

Edited by Zarklord
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
  • Create New...