Chickynoid networking help

Hi! I’ve been having a lot of fun with Chickynoid lately, but I’ve run into a problem that I can’t seem to find on the devforum.

I want to implement a mechanic in which you can click on the ground, and then you fling away from the direction you’re pointing at. The solution I’ve implemented using Chickynoid’s weapon system works fine, and it feels pretty smooth.

simulation.state.vel += ((direction * -50) + (Vector3.yAxis * 10))

This exact equation is fed to both the server and client (via ServerProcessCommand and ClientProcessCommand) and both call the same function. However, there seems to be something I’m doing wrong here.

The server keeps rolling back the client’s position to where the server believes the client should be. This creates jagged and unsatisfying movement, as you get teleported backwards whilst you are mid-air. I’ve debugged this on both the server and the client and their variables seem to be synced.
combined
Note the frequent yellow spikes in the corner.

I’ve tried turning it into something more akin to JumpPad (using :RegisterMoveState), but it had the same rollback problems as before. My strategy to fling the player is by adding directly to the Simulation’s velocity, using the camera’s direction as the launch direction. Adding to the Simulation’s velocity does seem to have an impact on the rollback frequency, as when directly setting the velocity, the rollback is lessened, but not completely eliminated.

simulation.state.vel = ((direction * -50) + (Vector3.yAxis * 10))

combined
Note the occasional yellow spikes in the corner.

I’m confused, as the JumpPads and general jumping work fine, but the tool that I’m creating is being finnicky.

3 Likes

Hi, so the reason why this happens is because unlike simulation’s process command function, as far as I know, weapons do not have the same complex techniques to sync it’s state between client and server.

Weapons only have a simple function that prevents the server from overriding it’s state for 0.5 seconds on client side.

The reason why I think these rollbacks happen is because chickynoid doesn’t do server reconciliation on weapons, so when the client tries to re-run the inputs to sync with the server, it doesn’t do it with the weapons, which produces a different output and causes this cycle of rollbacking which results in that jerky movement.

-- Some Example Server Script
local projectileData = {
			From = from,
			Direction = mouseDirection,
			Velocity = velocity,
			Character = player.Character,
			CompensationDelay = shootDelay,
		}
		
		player:SetAttribute('NextShoot', currentTime + shootDelayMS)
		
		targetWeapon:SetAttribute('Bullets',Bullets-1)
		for _, targetPlayer in Players:GetPlayers() do
			--if targetPlayer ~= player then
				Remotes.Shoot:Fire(targetPlayer, player, serverTime, projectileData)
			--end
		end
		
		local simulation = ProjectileSimulation.new(projectileData)
		

The simulation Module

--!strict
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local RunService = game:GetService('RunService')

local Signal = require(ReplicatedStorage.Packages.Signal)
local Trove = require(ReplicatedStorage.Packages.Trove)

local ProjectileSimulation = {}
ProjectileSimulation.__index = ProjectileSimulation

local MAX_RANGE: number = 1_000

export type ProjectileData = {
	From: CFrame,
	Direction: Vector3,
	Velocity: number,
	Character: Model,
	MaxRange: number?,
	CompensationDelay: number?
}

function ProjectileSimulation.new(data: ProjectileData)
	local self = setmetatable({
		_updateConnection = nil :: RBXScriptConnection?,
		
		Trove = Trove.new(),
		
		OnHit = Signal.new(),
		OnUpdate = Signal.new(),
		
		From = data.From,
		Direction = data.Direction,
		Velocity = data.Velocity,
		Character = data.Character,
		Position = data.From.Position + (data.Direction * data.Velocity * (data.CompensationDelay or 0)),
		MaxRange = data.MaxRange or MAX_RANGE,
	}, ProjectileSimulation)
	
	return self
end

function ProjectileSimulation.Disconnect(self: ProjectileSimulation)
	if not self._updateConnection then
		return
	end
	
	self.Trove:Remove(self._updateConnection)
	self._updateConnection = nil
end

function ProjectileSimulation.Connect(self: ProjectileSimulation)
	if self._updateConnection then
		return
	end
		
	self.OnUpdate:Fire(self.Position)
	
	self._updateConnection = self.Trove:Add(RunService.Heartbeat:Connect(function(deltaTime: number)
		self:Update(deltaTime)
	end))
end

function ProjectileSimulation.Update(self: ProjectileSimulation, deltaTime: number)
	self.Position += self.Direction * deltaTime * 15 * self.Velocity
	
	self.OnUpdate:Fire(self.Position)
	
	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.FilterDescendantsInstances = {self.Character,workspace.Raycast_Ignores}
	local raycast = workspace:Raycast(self.Position,self.Direction*(self.Velocity/4),params)

	if raycast then
		self.OnHit:Fire(raycast.Position,raycast.Instance)
		self:Destroy()
	end
	
	if (self.Position - self.From.Position).Magnitude > self.MaxRange then
		self:Destroy()
	end
end

function ProjectileSimulation.Destroy(self: ProjectileSimulation)
	self:Disconnect()
	self.Trove:Destroy()
end

export type ProjectileSimulation = typeof(ProjectileSimulation.new(nil :: any))

return ProjectileSimulation

client replicating it

local simulation = ProjectileSimulation.new(data)

simulation.OnUpdate:Connect(function(newPosition: Vector3)
		newProjectile:PivotTo(CFrame.lookAt(newPosition, target))
	end)

I had tried not using the weapons system prior to this post, using a similar system to JumpPad in which it hooks to the physics simulation, but it appeared to not work, so I scrapped it. What deeply confused me at the time was that both jumping and JumpPad worked completely fine despite also messing with velocity, so why doesn’t mine work?! I now realize that I was using variables outside of the physics simulation, which is what caused the replication to become finicky.

-- This will cause no issues, as it is using the physics simulation's variables.
if cmd.f > 0 and simulation.state.jump <= 0 then
	simulation.state.vel -= (cmd.fa - simulation.state.pos).Unit * 100
	simulation.state.jump = 0.5
end
-- This will cause issues, as it is using a variable outside of the physics simulation.
if cooldown > 0 then
	cooldown -= cmd.deltaTime
end
if cmd.f > 0 and cooldown <= 0 then
	simulation.state.vel -= (cmd.fa - simulation.state.pos).Unit * 100
	cooldown = 0.5
end

This is a really weird quirk in my opinion, it does sort of make sense to me, but I think I’m going to have to delve into the code more later to understand why it works this way. For now, I’ll just believe in the black magic.

I don’t know if you should still stick with chickynoid considering it is discontinued and roblox will release server authoritative physics beta by summer as far as I know (AuroraService).