Whenever certain Action goes on Cooldown, Client Framework allows certain keys to bypass and throw an error

Alright, I need to quickly update this, because really the issue isn’t up to date.

In specific the Enum.KeyCode.F, after the cooldown is done, something has the random chance of going for a further time before working again, which in turn allows this

if not table.find(Constants.WhitelistedKeybinds, Input.KeyCode) then print(Input.KeyCode) return end

To become bypassed, but I have no understanding as to why.

image

Now the issue is sincerely confusing, because it’s more of a logic issue than a minor spelling.

I will now provide all of the scripts which are in use, but be aware, these scripts are pretty long, the Cooldown Script being the longest.

Client - Local Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local PlrControllerModule = require(ReplicatedStorage.Modules.Client)

UserInputService.InputBegan:Connect(PlrControllerModule.FunctionSender) 
UserInputService.InputEnded:Connect(PlrControllerModule.FunctionSender)

Client - Module Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")

local ActionContainer = ReplicatedStorage.Modules.ActionContainer
local Shared = ReplicatedStorage.Modules.Shared

local BridgeNet = require(ReplicatedStorage.Libraries.BridgeNet2)
local Constants = require(ReplicatedStorage.Modules.WhitelistedInputs)

local Held = false

local ActionHandler_C = {
	["CombatActions_C"] = require(ActionContainer.CombatActions_C),
	["MovementActions_C"] = require(ActionContainer.MovementActions_C)
}

local function FuncSender(InputData : { })
		local Actionnare, CurrentAction	
		CurrentAction = function() -- method of directly executing one of the actions
		for ActionsIndex, PickedClass in pairs(ActionHandler_C) do
			if type(PickedClass) == "table" then -- Checks the table whether it's an actual table or not
				if PickedClass[InputData.Key] then
					Actionnare = PickedClass[InputData.Key] -- Gets the key of the specific action
					print(Actionnare)
					return require(ActionContainer[ActionsIndex].Functions_C)[Actionnare["Name"]] -- Play the Action from the Functions (They are all named this for convenience 
					end
				end
			end
		end
	
	local Action = CurrentAction()
	
	Action()
end

local InputClient = {}

function InputClient.FunctionSender(Input: InputObject, GameProcesssedEvents : BoolValue )
	
	if GameProcesssedEvents then	return end

	if not table.find(Constants.WhitelistedUserInputTypes, Input.UserInputType) then return end
	if not table.find(Constants.WhitelistedKeybinds, Input.KeyCode) then print(Input.KeyCode) return end
		
	FuncSender(({
		Type = Input.UserInputType,
		State = Input.UserInputState,
		Key = (Input.KeyCode == Enum.KeyCode.Unknown and Input.UserInputType or Input.KeyCode)
		
	}))	
	print(Input.KeyCode == Enum.KeyCode.Unknown and Input.UserInputType or Input.KeyCode)
end


return InputClient

Combat Actions Homebase

local Functions = require(script.Functions_C)

local CC = {
	[Enum.UserInputType.MouseButton1] = {Name = "M1", Cooldown = .15};
	--[Enum.UserInputType.MouseButton2] = {};
	[Enum.KeyCode.F] = {Name = "Blocking", Cooldown = .5};
	--[Enum.KeyCode.C] = {Name = "Dodging", Cooldown = 1.5};
	["W + M1"] = {Name = "Running Punch", Cooldown = 5},


}

return CC

Actual Combat Actions

-- Private Variables
local ReplicatedStorage = game:GetService("ReplicatedStorage");
local Players = game:GetService("Players");
local UserInputService  = game:GetService("UserInputService");

local Shared = ReplicatedStorage.Modules.Shared;

local BridgeNet = require(ReplicatedStorage.Libraries.BridgeNet2);

local Animation_C = require(Shared.CombatActions_CM.Animation_C);
local CoolDownHandler = require(Shared.CombatActions_CM.Cooldowns);
local WrapperPlayer = require(ReplicatedStorage.Modules.Shared.PlayerModules.WrapperPlayer_C)

local Player = Players.LocalPlayer;
local Character = Player.Character or Player.CharacterAdded:Wait();
local Humanoid = Character:WaitForChild("Humanoid");

local ClientRemote = BridgeNet.ClientBridge("ClientRemote")

local Count = 0;
local Held = false;
local Animation, Length;

local CombatActions_C = {

	["M1"] = function(Parameters)
		if CoolDownHandler:CheckCD(Player, {"M1", "M1StringFinish"}) == false and not WrapperPlayer:CheckStateActivities(Player, "Attacking") then
			
			if Count >= 4 then
				Count = 1
			else
				Count += 1
			end;

			if Count == 4 then
				CoolDownHandler:CreateCooldown(Player, "M1StringFinish", Length * 5)
			end;

			Animation, Length = Animation_C.getmultipleAnimation(Character, Count, Animation_C.CombatAnimations);
			Animation:Play()

			CoolDownHandler:CreateCooldown(Player, "M1", Length * 1.5)

			Animation:GetMarkerReachedSignal("HitboxSummon"):Connect(function()			
				ClientRemote:Fire({Input = "M1", Player = Player, Count = Count, Length = Length})
			end);
			
		end
	end;
	
	["Dodging"] = function(Parameters)
		ClientRemote:Fire({Input = "Dodging", Player = Player})
	end;
	
	["Blocking"] = function(Parameters)
		
		if UserInputService:IsKeyDown(Enum.KeyCode.F) and (not CoolDownHandler:CheckCD(Player, {"Blocking"})) == true then
			ClientRemote:Fire({Input = "Blocking", Player = Player, Held = true})
		end
		
		if not UserInputService:IsKeyDown(Enum.KeyCode.F) and (not CoolDownHandler:CheckCD(Player, {"Blocking"})) == true then
			ClientRemote:Fire({Input = "Blocking", Player = Player, Held = false})
			CoolDownHandler:CreateCooldown(Player, "Blocking", 1)
		end
	end,

}

return CombatActions_C

Beware- The longest script - Cooldown Module

local Debris = game:GetService("Debris")
local CreateInstanceValue = true

local CooldownModule = {}


local CooldownHandler = {
	Cooldowns = {}
}



local clocky = os.clock

local function CreateNewValue(Name, Time)
	local StringValue = Instance.new("StringValue")
	StringValue.Name = Name
	StringValue.Value = "Cooldown"
	StringValue.Parent = script

	if Time then
		Debris:AddItem(StringValue, Time)
	end

	return StringValue
end


function CooldownModule:CreateCooldown(Player, Name, Length)
	local IndividualCharacterCD = CooldownHandler.Cooldowns[Player]

	if not IndividualCharacterCD then
		CooldownHandler.Cooldowns[Player] = {}
		IndividualCharacterCD = CooldownHandler.Cooldowns[Player]
	end


	local RealCD = IndividualCharacterCD[Name]

	if not RealCD then
		IndividualCharacterCD[Name] = {}
		RealCD = IndividualCharacterCD[Name]
	end



	local CDProperties = {}
	CDProperties[1] = Length
	CDProperties[2] = clocky()
	CDProperties[3] = CreateInstanceValue and CreateNewValue(Player.Character.Name .. "_" .. Name, Length) or nil


	local CooldownPos = (#RealCD + 1)
	table.insert(RealCD, CooldownPos, CDProperties)
end

function CooldownModule:CheckCD(Player, Name)

	Name = typeof(Name) == "string" and {Name} or Name


	if CreateInstanceValue then
		for _, v in ipairs(Name) do
			if script:FindFirstChild(Player.Character.Name .. "_" .. v) then
				return true
			end
		end
	end

	local IndividualCharacterCD = CooldownHandler.Cooldowns[Player]
	if not IndividualCharacterCD  then
		CooldownHandler.Cooldowns[Player] = {}
		return false
	end


	for i = 1, #Name do
		local CooldownName = Name[i]
		local Index = IndividualCharacterCD[Name]

		if Index then

			for i, v in pairs(Index) do

				if clocky() - v[2] > v[1] then
					return true
				else
					table.remove(IndividualCharacterCD[CooldownName], i)
					IndividualCharacterCD[CooldownName] = nil
				end
			end
		end
	end

	return false
end

return CooldownModule

See, the issue is that I cannot pinpoint the main issues possibly origins, even though I don’t find my script extremely confusing.

1 Like

Unfortunately, one more bump.

These characters will be blurred

Kind of hard to tell where it’s erroring. What is line 33 on ReplicatedStorage.Modules.Client?

return require(ActionContainer[ActionsIndex].Functions_C)[Actionnare["Name"]] -- Play the Action from the Functions (They are all named this for convenience 

In specific this line.

In addition, this error only happens, once the cooldown’s bug, or so I learned. So I don’t even know if this title is up to date at this point.

I feel like I also need to further the explanation of that whole chunk of code, but I don’t know if the comments just do it justice in of itself.

Correct me if I’m wrong, but where exactly does “Combat Actions Homebase” get used in your Client ModuleScript?
As far as I’m reading, you’re iterating through ActionHandler_C, which is a dictionary with a key String and a value Table of functions because you are using CombatActions_C (Actual Combat Actions), which is supposed to be CC (Combat Actions Homebase)?

local ActionHandler_C = {
	["CombatActions_C"] = require(ActionContainer.CombatActions_C),
	["MovementActions_C"] = require(ActionContainer.MovementActions_C) -- irrelevant
}
-- Equivalent to
local ActionHandler_C = {
	["CombatActions_C"] = { -- Actual Combat Actions instead of Combat Actions Homebase?
        ["M1"] = function() end, -- ...
        ["Dodging"] = function() end, -- ...
        -- ...
    },
	["MovementActions_C"] = {} -- ... (irrelevant)
}

local function FuncSender(InputData : { })
    local Actionnare, CurrentAction	= nil
    CurrentAction = function()
        for ActionsIndex, PickedClass in pairs(ActionHandler_C) do -- Iterating through ActionHandler_C (meaning PickedClass's values are functions, not tables)

Because of this, Actionnare is expected to be a table with a key Name, but instead is a function.

In particular, the homebase is getting used for these two things

[Enum.UserInputType.MouseButton1] = {Name = "M1", Cooldown = .15};

It’s name, (and soon), it’s cooldown.

To find the specific action, I input the name into the Functions_C related to the KeyCode or UserInputType, and get to access the function by inputting the Name to a key for the particular Function action, in this case [Enum.KeyCode.F] = {Name = "Blocking", Cooldown = .5}; blocking

But isn’t your for loop supposed to be going through CC (Combat Actions Homebase) and not CombatActions_C (Actual Combat Actions)? From what I’m reading, PickedClass is a table of functions, not the table containing Name and Cooldown.

for ActionsIndex, PickedClass in pairs(ActionHandler_C) do -- isn't PickedClass a table of functions, not a table of tables with the Name and Cooldown?

Wouldn’t it be something like this instead?

local ActionHandler_C = {
	["CombatActions_C"] = require(ActionContainer.CombatActions_C),
	["MovementActions_C"] = require(ActionContainer.MovementActions_C)
}

local Homebase = {
	["CombatActions_C"] = { -- require(path to CC homebase module)
		[Enum.KeyCode.F] = {
			["Name"] = "Blocking",
			["Cooldown"] = 0.5,
		},
		-- etc.
	},
	["MovementActions_C"] = {} -- ...
}

local function FuncSender(InputData : { })
	local action = (function()
		for ActionsIndex, PickedClass in pairs(Homebase) do -- Homebase instead of ActionHandler_C
			-- no need to check type of PickedClass assuming everything is a table anyways
			if PickedClass[InputData.Key] ~= nil then
				return require(ActionContainer[ActionsIndex].Functions_C)[PickedClass[InputData.Key].Name]
			end
		end
	end)()
	if action ~= nil then
		action()
	end
end

PickedClass is the table, containing the Name and Cooldown. The way functions is able to be accessed, is through this confusing return

return require(ActionContainer[ActionsIndex].Functions_C)[Actionnare["Name"]]

But, I will break it down

ActionContainer holds all of the Tables with Name and Cooldown’s, and the children are the actual actions.

The CC (Combat Actions Homebase), need the index, and in particular it is scoped by the multitude of if statements. From there, we go into the actual functions, which is under these specific modules or Homebases. This is where the actual Functions are located, and from there, using the Key’s Name, the function related to the index can be called.

Honestly looking through this, I really do have a weirdly confusing setup to anyone besides me.

The setup is also a bit more organized to me, but does have confusing abstractions when trying to explain it raw to someone whose never seen such an architecture.

I understand how you are trying to do things, but your naming is just not consistent throughout, and quite a lot of bits are missing in this post to solve the actual issue.

Your variable in the Actual Combat Actions section is called CombatActions_C, which is a table of functions. The variable name CombatActions_C is also being used as a key in ActionHandler_C, which intuitively is supposed to mean ActionHandler_C.CombatActions_C == CombatActions_C, but instead ActionsHandler_C.CombatActions_C is actually CC from the section Combat Actions Homebase.

As for your problem, if it’s working the first time, are you making sure you are not accidentally removing a function from ActionContainer[ActionsIndex].Functions_C or changing the value of Name in another script?

Yeah, sadly I haven’t gotten a proper way of naming yet, after this can hopefully get fixed, I can solve that in some way.

Name is a constant, so that never changes. Instead, what I figured out was really that, once the CoolDown on the Blocking Action glitches (Which I cannot pinpoint as to why it even does ths), it basically just breaks the script, and allows the non-whitelisted keys to bypass this return

if not table.find(Constants.WhitelistedKeybinds, Input.KeyCode) then print(Input.KeyCode) return end

Leading to errors just being thrown.

There’s no proper pattern to when the blocking glitches, the only thing that allows me to know that I’m about to get these errors thrown is when I keep pressing F, and I begin to see, that it isn’t blocking even after spamming it a little, then when given enough time, it just begins to allow all the non-whitelisted inputs to bypass the return.

Then it would be near impossible to tell just by reading without proper debugging.
Spam readable prints on everything to pinpoint where it goes wrong, ie:

  1. InputClient.FunctionSender gets called, print FunctionSender called: <Input>
  2. Check if an input has passed or failed the whitelist check. Instead of printing just the Input.KeyCode, print Key passed/failed: <Input.KeyCode> instead. If your table.find fails, print to see if Constants.WhitelistedKeybinds is even the same as the 1st time and if Input is valid.
  3. In your loop, check if PickedClass[InputData.Key] is being found.
  4. If PickedClass[InputData.Key] is found, print the entire thing to see what you get.
  5. Check if require(ActionContainer[ActionsIndex].Functions_C)[Actionnare["Name"]] is being found. If not, print the entirety of require(ActionContainer[ActionsIndex].Functions_C) to see if it gets altered somehow.
  6. Print every table being used after running your action event, to see if things are unaltered.

Do the same with your action function and cooldown. For every step, print print print.

This way, you can see what steps are failing after the second action.

I’ve ended up doing this to an unfortunate fault. Nothing properly showed me anything. At times, the InputData.Key in the loop didn’t loop, yet the Action() still went off.

Used to print PickedClass[InputData.Key], it worked pretty well, it continued to not let the non-whitelisted key continue.

I will try 2 of course, and the other’s I haven’t tried. Thank you so much for the assistance so far though. Do you have any articles, relating on good Naming practices, or any advice before I go into this deep debug?

Read the Roblox Lua Style guide if you have the time, but just code in a way where things are intuitive. It’s okay if your variables are kind of long.

Here’s a small example on how I typically name my variables:

Instead of using for i(2), v(2), I named my variables accordingly so I wont ever get confused on which one is which. I also use underscores, ie self._configuration, for things where I “interally use” (technically everything is internally being used), such as:

local Settings = {
    ["Keybinds"] = { -- Things the player can change
        ["Drop"] = Enum.KeyCode.Q,
        ["Reload"] = Enum.KeyCode.R
    },
    ["_blacklistedKeybinds"] = { -- Things I use internally
        Enum.KeyCode.W,
        Enum.KeyCode.A,
        Enum.KeyCode.S,
        Enum.KeyCode.D
    }
}
1 Like

Such a dumb little slip-up on my part. It’s just more confusing how it didn’t error at the beginning, only doing it once my Blocking script began. Thank you UIScript for your assistance.

(In short, Enum.KeyCode.W was a constant, and it was never finding a proper function, but this would only occur after the blocking function was executed)

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.