How do I implement rollback netcode in Roblox?

Hello guys, as you can tell from the title I am struggling with implementing rollback netcode in a roblox environment. Here is the code I have right now:

local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local InputRelay = game.ReplicatedStorage.InputRelay
local Player = game.Players.LocalPlayer
local Character: Model = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart: Part = Character:WaitForChild('HumanoidRootPart')

local inputStorage = {}
local currentLocalInput = "Idle"
local OppositePlayerInput
local OppositePlayerFrame
local currentFrame = 0
local MAX_ENTRIES = 7

local function getOppositePlayer(player: Player)
	local oppositePlayer: Player

	for _, players: Player in pairs(game:GetService("Players"):GetPlayers()) do
		if players.Name ~= player.Name then
			oppositePlayer = players
		end
	end

	return oppositePlayer
end

local function SimulateLocalMovement(Name: string)
	local inputFunctionTable = {
		['W'] = function()
			HumanoidRootPart.CFrame = HumanoidRootPart.CFrame + HumanoidRootPart.CFrame.LookVector*5
		end,
		['S'] = function ()
			HumanoidRootPart.CFrame = HumanoidRootPart.CFrame - HumanoidRootPart.CFrame.LookVector*5
		end
	}
	
	if inputFunctionTable[Name] then
		inputFunctionTable[Name]()
	end
end

local function SimulateNewState(correctedInput: string)
	local currentCFrame = getOppositePlayer(Player).Character:WaitForChild('HumanoidRootPart').CFrame
	
	local calculateTable = {
		["W"] = function ()
			local newCFrame = currentCFrame + currentCFrame.LookVector*5
			return newCFrame
		end,
		["S"] = function()
			local newCFrame = currentCFrame - currentCFrame.LookVector*5
			return newCFrame
		end,
		["Idle"] = function()
			return currentFrame
		end,
	}
	
	return calculateTable[correctedInput]
end

local function addEntry(specTable, entry)
	table.insert(specTable, entry)
	-- Check if the array exceeds the maximum limit
	if #specTable > MAX_ENTRIES then
		-- Remove the oldest entry (first entry in the array)
		table.remove(specTable, 1)
	end
end


UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean) 
	if gameProcessedEvent then return end
	if input.UserInputType == Enum.UserInputType.Keyboard then
		currentLocalInput = input.KeyCode.Name
		SimulateLocalMovement(currentLocalInput)
	end
end)


UIS.InputEnded:Connect(function(input: InputObject, gameProcessedEvent: boolean) 
	if gameProcessedEvent then return end
	if input.UserInputType == Enum.UserInputType.Keyboard then
		currentLocalInput = "Idle"
	end
end)

InputRelay.OnClientEvent:Connect(function(InputName: string, oppositeFrame: number)
	addEntry(inputStorage, {oppositeFrame, InputName})	
	
	for _, v in pairs(inputStorage) do
		if v[1] == oppositeFrame and v[2] ~= InputName then
			print(`Rollback! {v[2]} at {v[1]} does not equal {InputName} at {oppositeFrame}`)
			v[2] = InputName -- Correct the wrongly predicted statement
			-- Simulate the player's state (position as of now)
			-- Take the player's current CFrame and calculate their new CFrame using the newly corrected input
			local simulatedCFrame = SimulateNewState(v[2])
			-- Render
			-- Set the player's CFrame with the newly calculated CFrame
			getOppositePlayer(Player).Character:WaitForChild('HumanoidRootPart').CFrame = simulatedCFrame
		end
	end
end)

RunService.RenderStepped:Connect(function(deltaTime: number) 
	currentFrame += 1
	InputRelay:FireServer(currentLocalInput, currentFrame)	
	
	-- Predict other player's input based off of their previous inputs, if there are no current inputs, start at "Idle" on frame 1.
	
	if #inputStorage < 1 then
		addEntry(inputStorage, {currentFrame, "Idle"})
	else
		addEntry(inputStorage, {currentFrame, inputStorage[#inputStorage][2]})
	end
end)

Here is what the current game looks like:

Any help would be amazing. Please and thank you.

1 Like