Jump to content

Host and client interaction confusion


Recommended Posts

Heyy, it's the smashup guy here. Last week I decided to throw the code I have into DST just to see what I could do with it, but I am quickly remembering just how little I actually know about this version of Don't Starve. I've spent the better half of this week studying it and I just... don't get it.

I can't get the client to interact with the host the way I want it to. The client can barely do anything. I know that everything for the client needs to happen before all that pristine and ismastersim stuff happens, but I still can't get it to work correctly. I can get the client's stategraph states to work when called from certain places, but not from key handlers initiated before pristine. 

So I started again with baby steps to figure out whats wrong. Like, the babiest of baby steps. I made a keydownhandler in the modmain to make a button that spawns nitre and teleports it to any player's location. It didn't work. It shows up fine when used by the host, but for the client, the nitre only appears on their screen. Why is that? I'm honestly not sure where else to go from here, I can't think of any smaller steps to take.

I guess I should just start with the basics and just ask "how do I spawn a prefab as a client?"

Link to comment
Share on other sites

5 hours ago, pickleplayer said:

how do I spawn a prefab as a client?

You don't.

Clients have limited interaction, you can't just spawn a prefab only on client's side. If you want everything to be usable/visible for all players, then make it serverside.

Here's what happens for the client when you spawn an item:

local inst = CreateEntity()

inst.entity:AddTransform()
inst.entity:AddAnimState()
inst.entity:AddSoundEmitter()
inst.entity:AddNetwork()

inst.AnimState:SetBank("bank")
inst.AnimState:SetBuild("build")
inst.AnimState:PlayAnimation("anim")
	
inst.entity:SetPristine()
	
if not TheWorld.ismastersim then
	return inst
end

And that's it. You can see the item, but TheWorld is not master on client, and that's where the stuff ends for him. The rest is handled serverside, but since client spawned it, the item "exists" only for him.

Clients don't have components (well, there's a few exceptions), so they can't initiate them. That's why the item doesn't work at all.

Most (if not all) functions, etc. should run serverside.

Edited by PanAzej
Link to comment
Share on other sites

14 hours ago, PanAzej said:

You don't.

Clients have limited interaction, you can't just spawn a prefab only on client's side. If you want everything to be usable/visible for all players, then make it serverside.

Here's what happens for the client when you spawn an item

And that's it. You can see the item, but TheWorld is not master on client, and that's where the stuff ends for him. The rest is handled serverside, but since client spawned it, the item "exists" only for him.

Clients don't have components (well, there's a few exceptions), so they can't initiate them. That's why the item doesn't work at all.

Most (if not all) functions, etc. should run serverside.

 

Ohhh, alright. So any prefabs spawned by clients will just not exist for anyone but themselves.

But what about Wes's balloons? The balloon prefabs don't look much different from any other basic prefab's code. And the balloonmaker component doesn't do anything more than spawn the balloon prefab and teleport it to his location. So what makes it different? Why does that one show up for host just fine?

Does it have anything to do with the "balloonmancer" tag that is added onto Wes before the pristine sets in? 

I have a component that sets in before pristine that adds all of the keydown handlers that push the events the custom stategraph needs to function, and it still doesn't show up for host. Is that not how you add a component server side?

Edited by pickleplayer
Link to comment
Share on other sites

43 minutes ago, pickleplayer said:

 

Ohhh, alright. So any prefabs spawned by clients will just not exist for anyone but themselves.

But what about Wes's balloons? The balloon prefabs don't look much different from any other basic prefab's code. And the balloonmaker component doesn't do anything more than spawn the balloon prefab and teleport it to his location. So what makes it different? Why does that one show up for host just fine?

Does it have anything to do with the "balloonmancer" tag that is added onto Wes before the pristine sets in? 

I have a component that sets in before pristine that adds all of the keydown handlers that push the events the custom stategraph needs to function, and it still doesn't show up for host. Is that not how you add a component server side?

It has an action associated with it, and an RPC.

The action lets the user right click the item and the RPC is sent in the action callback to let the server know the client's intention of making a balloon.

Link to comment
Share on other sites

57 minutes ago, pickleplayer said:

But what about Wes's balloons? The balloon prefabs don't look much different from any other basic prefab's code. And the balloonmaker component doesn't do anything more than spawn the balloon prefab and teleport it to his location. So what makes it different? Why does that one show up for host just fine?

Does it have anything to do with the "balloonmancer" tag that is added onto Wes before the pristine sets in? 

Here's step-by-step what happens with Wes's balloons:

1. ComponentAction:

INVENTORY = --args: inst, doer, actions, right
{
	balloonmaker = function(inst, doer, actions)
		if doer:HasTag("balloonomancer") then
			table.insert(actions, ACTIONS.MAKEBALLOON)
		end
	end
}

That's what this "balloonomancer" tag is for in Wes's prefab. Componentaction checks are run on clients, and since clients don't have components, you have to rely on either tags or replicable components. This thing here makes it so we see the prompt for "Make Balloon" action.

2. Actionhandler:

ActionHandler(ACTIONS.MAKEBALLOON, "makeballoon")

When we click - we're sent to the stategraph state through this ActionHandler.

3. Stategraph State:

State{
        name = "makeballoon",
        tags = { "doing", "busy", "nodangle" },

        onenter = function(inst, timeout)
            inst.sg.statemem.action = inst.bufferedaction
            inst.sg:SetTimeout(timeout or 1)
            inst.components.locomotor:Stop()
            inst.SoundEmitter:PlaySound("dontstarve/common/balloon_make", "make")
            inst.SoundEmitter:PlaySound("dontstarve/common/balloon_blowup")
            inst.AnimState:PlayAnimation("build_pre")
            inst.AnimState:PushAnimation("build_loop", true)
        end,

        timeline =
        {
            TimeEvent(4 * FRAMES, function(inst)
                inst.sg:RemoveStateTag("busy")
            end),
        },

        ontimeout = function(inst)
            inst.SoundEmitter:KillSound("make")
            inst.AnimState:PlayAnimation("build_pst")
            inst:PerformBufferedAction()
        end,

        events =
        {
            EventHandler("animqueueover", function(inst)
                if inst.AnimState:AnimDone() then
                    inst.sg:GoToState("idle")
                end
            end),
        },

        onexit = function(inst)
            inst.SoundEmitter:KillSound("make")
            if inst.bufferedaction == inst.sg.statemem.action then
                inst:ClearBufferedAction()
            end
        end,
    },

inst:PerformBufferedAction() is where we're sent to... well, perform our buffered action.

4. Action.

ACTIONS.MAKEBALLOON.fn = function(act)
    if act.doer ~= nil and
        act.invobject ~= nil and
        act.invobject.components.balloonmaker ~= nil and
        act.doer:HasTag("balloonomancer") then
        if act.doer.components.sanity ~= nil then
            if act.doer.components.sanity.current < TUNING.SANITY_TINY then
                return false
            end
            act.doer.components.sanity:DoDelta(-TUNING.SANITY_TINY)
        end
        --Spawn it to either side of doer's current facing with some variance
        local x, y, z = act.doer.Transform:GetWorldPosition()
        local angle = act.doer.Transform:GetRotation()
        local angle_offset = GetRandomMinMax(-10, 10)
        angle_offset = angle_offset + (angle_offset < 0 and -65 or 65)
        angle = (angle + angle_offset) * DEGREES
        act.invobject.components.balloonmaker:MakeBalloon(
            x + .5 * math.cos(angle),
            0,
            z - .5 * math.sin(angle)
        )
        return true
    end
end

Action itself is executed serverside. And that's the end here.

---

I haven't toyed around with KeyHandlers, though I'd say you should look how these are done in mods like Playable Pets or Puppy Princess.

Link to comment
Share on other sites

@PanAzej Forgot the RPC that actually networks it to the server.

In this case right-clicking on the stack of balloons calls RPC.UseItemFromInvTile with the ACTIONS.MAKEBALLOON.code.

RPC.UseItemFromInvTile calls inventory:UseItemFromInvTile which eventually gets to a buffered action.

Link to comment
Share on other sites

Alright! Thank you for the explanation about actions and RPCs and links to mods.

I've been going through it all and I'm still figuring out RPCs, but I managed to get a key handler event to work between client and host with that new knowledge, so that's good. 

So I know how to get the client to send events to the host, but now I'm wondering if there's any way for the host to detect if the client is holding a button down. I'll have to look into that at some point.

Guess I'll be spending a while reworking the control scheme until I run into any other problems. Thanks!

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...