CFrames stored in my module table do not have .Position and .Rotation?

  1. What do I want to achieve? Compare .Rotation of 2 CFrames

  2. What is the issue? The CFrame which is in my module table errors for an attempt to index nil with .Position

  3. What solutions have I tried so far? Quick search on the forum, tested the same operation outside of the module (works)

I’m working on a time rewind ability and need to log each new CFrame of the player’s HumanoidRootPart. I do this in a module table; However, the HRP CFrame seems to undergo minor changes in rotation every frame, resulting in the same CFrame being added with slight positive or negative variations in the orientation’s X & Z axes. I need a way to prevent this, but attempting to check the rotation doesn’t work because it’s nil.

A place file in which you can reproduce the error: Time Rewind.rbxl (70.0 KB)

Alternatively, the module script:

local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")

local ControlModule = require(game:GetService("Players").LocalPlayer.PlayerScripts.PlayerModule):GetControls()

local CachedFind = table.find
local CachedInsert = table.insert
local CachedWait = task.wait

local INITIAL_FX_PROPERTIES = {
	ColorCorrectionEffect = {
		Name = "TimeRewind_ColorCorrection",
		Parent = game:GetService("Lighting"),
		Enabled = false,
		Brightness = -0.15,
		Contrast = -0.25,
		Saturation = -1.5,
		TintColor = Color3.fromRGB(112, 66, 20)
	},
}

local TimeControl = {
	root_cframes = {},
	IsRewinding = false,
	IsMoving = false
}

function TimeControl:new(character)
	self.Character = character
	self.Humanoid = character.Humanoid
	self.HumanoidRootPart = character.HumanoidRootPart
	self.RunAnimation = self.Humanoid.Animator:LoadAnimation(character.Animate.run.RunAnim)

	self.ColorCorrection = Instance.new("ColorCorrectionEffect")
	self.ColorCorrection.Name = INITIAL_FX_PROPERTIES.ColorCorrectionEffect.Name
	self.ColorCorrection.Enabled = INITIAL_FX_PROPERTIES.ColorCorrectionEffect.Enabled
	self.ColorCorrection.Brightness = INITIAL_FX_PROPERTIES.ColorCorrectionEffect.Brightness
	self.ColorCorrection.Saturation = INITIAL_FX_PROPERTIES.ColorCorrectionEffect.Saturation
	self.ColorCorrection.TintColor = INITIAL_FX_PROPERTIES.ColorCorrectionEffect.TintColor
	self.ColorCorrection.Parent = INITIAL_FX_PROPERTIES.ColorCorrectionEffect.Parent
	
	RunService.Heartbeat:Connect(function(_)
		if not self.IsRewinding and not CachedFind(self.root_cframes, self.HumanoidRootPart.CFrame) then
			local latestTableCFrame = self.root_cframes[#self.root_cframes]
			print(`Latest in table | Position: {latestTableCFrame.Position}, Rotation: {latestTableCFrame.Rotation}`)
			print(`HRP: {self.HumanoidRootPart.CFrame.Position}`)
			
			CachedInsert(self.root_cframes, self.HumanoidRootPart.CFrame)
		end
	end)

	return setmetatable(TimeControl, {
		__index = function(_, index)
			return rawget(TimeControl, index:lower())
		end
	})
end

function TimeControl:rewindtime(shouldEnableRewind)	
	local CurrentTween

	local function _Cleanup()
		if not self.IsRewinding then 
			warn("Time is not rewinding (already cleaned up?)")
			return 
		end

		self.IsRewinding = false
		self.ColorCorrection.Enabled = false

		if CurrentTween then
			CurrentTween:Destroy()
		end

		self.Humanoid.AutoRotate = true
		ControlModule:Enable()	
		self.RunAnimation:Stop()
	end	

	if shouldEnableRewind then
		if self.IsRewinding then
			warn("Time is already rewinding.")
			return
		end

		self.IsRewinding = true
		self.ColorCorrection.Enabled = true

		self.Humanoid.AutoRotate = false
		ControlModule:Disable()
		self.RunAnimation:Play()

		for index = #self.root_cframes, 1, -1 do
			if not self.IsRewinding then break end

			CurrentTween = TweenService:Create(
				self.HumanoidRootPart,
				TweenInfo.new(0.001),
				{CFrame = self.root_cframes[index]}
			)

			self.root_cframes[index] = nil

			CurrentTween:Play()
			CurrentTween.Completed:Wait()
		end
	end

	_Cleanup()
end

return TimeControl

Maybe try creating a new CFrame to store using CFrame.new() with the freshly indexed CFrame.Position/Rotation values?

I just noticed I try to get the properties even when the first index in the table is nil…

if not tableItem then continue end
-- or
if tableItem == nil then continue end

can save lives

EDIT: Ignore this for a bit, I may be stupid

Yes… This turned into an entirely different issue now, do I just say solved here and make another post if I can’t solve it?

I can’t reliably detect changes in the CFrame cause the differences are less than 0.000000000001 sometimes. This is the loop at the moment:

	RunService.Heartbeat:Connect(function(_)
		if not self.IsRewinding and not CachedFind(self.root_cframes, self.HumanoidRootPart.CFrame) then
			if #self.root_cframes > 0 then
				local latestTableCFrame = self.root_cframes[#self.root_cframes]
				local _, yOrientationNew, _ = self.HumanoidRootPart.CFrame.Rotation:ToEulerAnglesYXZ()
				local _, yOrientationOld, _ = latestTableCFrame.Rotation:ToEulerAnglesYXZ()
				local difference = math.abs(yOrientationNew - yOrientationOld)
				
				print(`Enough of a difference?: {difference > 0.000000000001}`)
				if not (difference > 0.000000000001) then return end
			end

			CachedInsert(self.root_cframes, self.HumanoidRootPart.CFrame)
		end
	end)