Input puzzle system like Helldivers not working

helo guys! out of love for the game and a stagnation of development for my extraction shooter, i decided to spend the last day of my weekend trying to recreate the Stratagem input system from Helldivers 2.

an example of how it looks in HD2 is shown below

basically, i have a module script that holds all the directions a player must input for a specific stratagem. i want it so that it replicates the ingame stratagem selector system, so when you input any key it will color it in with the active input color as long as it is in the same position relative to the input_map’s lenght as it is to the next step index (if the next input index = 1 and the first key in the stratagem UI has the same index and the same direction, then it would be colored in). i also want it to return any inputs back to white if the inputs the player puts in strays away from what the input_map requires for the stratagem to be called (if i go left and a stratagem starts with left, but i go down and it requires right for the second input, then it returns that stratagem’s input colors back to white).

sorry if this is a bit confusing but im running on insomnia induced fumes and fried brains because ive been trying to fix this for half the day :sob:.
it basically doesnt work but im trying to replicated and need any help, so if u can help me either design the structure of the code itself or how to properly index and check stuff, i would really appreciate it, thanks

i want the system to be dynamic so i can easily create new stratagem input maps and stuff, but i cant even really grasp how to make it in the first place, even though i had a vague thought/idea in my head of how i would structure it (this is the second idea i tried after an hour long break after a few hours of trying to debug and make stuff work)

input maps module script
local stratagem_blueprints = {}



stratagem_blueprints.fivehundredkilo_bomb = {"up","right","down","down","down"}
stratagem_blueprints.hellbomb = {"down","up","left","down","up","right","down","up"}

return stratagem_blueprints

hierarchy of the GUI you use:

gui

stratagem input script
-- player
local plr = game.Players.LocalPlayer

-- input colors
local idle_input_col = Color3.fromRGB(255, 255, 255)
local activated_input_col = Color3.fromRGB(255, 200, 0)

--
local up_input_id = "rbxassetid://90729986968561"
local right_input_id = "rbxassetid://90271689553778"
local down_input_id = "rbxassetid://136041977216996"
local left_input_id = "rbxassetid://134806224313272"

-- ui
local requesting_stratagem = true
local next_input_step = 1

local stratagem_ui = plr.PlayerGui.stratagems_gui
local stratagem_list = stratagem_ui:WaitForChild("stratagems")

local sounds = stratagem_ui:WaitForChild("sounds")
local input_sounds = sounds:WaitForChild("input_sounds")


-- services
local uis = game:GetService("UserInputService")
local repstorage = game:GetService("ReplicatedStorage")

-- events
local call_in_stratagem = repstorage:WaitForChild("call_in_stratagem")
local stratagem_input_success = repstorage:WaitForChild("stratagem_input_success")

-- modules
local input_maps = require(script.stratagem_input_maps)

function fail_input(inputs_list)
	for _,input in ipairs(inputs_list:GetChildren()) do
		input.ImageColor3 = idle_input_col
	end
end

function reset_all()
	for _,stratagem in ipairs(stratagem_list:GetChildren()) do
		fail_input(stratagem:WaitForChild("inputs_list"))
	end
	next_input_step = 1
end

function finish_check(stratagem_input_map)
	if next_input_step == #stratagem_input_map then
		print("FINISHED A STRATAGEM")
	else
		next_input_step += 1
	end
end

function inputs_scan(dir, input_id)
	for _,stratagem_blueprint in pairs(input_maps) do
		if stratagem_blueprint[next_input_step] == dir then
			print("next thingy equals next input and direction")
			for _,stratagem in ipairs(stratagem_list:GetChildren()) do
				local previ = stratagem:FindFirstChild("inputs_list"):FindFirstChild("input_"..tostring(next_input_step - 1))
				if previ then
					print(previ.Name)
					if previ.ImageColor3 ~= activated_input_col then
						print("FAIL FROM COLOR THING")
					end
				end

				if stratagem:FindFirstChild("inputs_list"):WaitForChild("input_"..tostring(next_input_step)).Image == input_id then
					stratagem:FindFirstChild("inputs_list"):FindFirstChild("input_"..tostring(next_input_step)).ImageColor3 = activated_input_col
					finish_check(stratagem_blueprint)
				else
					print("failed!!!!")
					fail_input(stratagem:FindFirstChild("inputs_list"))
				end				
			end
		else
			reset_all()
		end
	end
end

-- uis
uis.InputBegan:Connect(function(key, gp)
	if gp then return end

	if key.KeyCode == Enum.KeyCode.W and requesting_stratagem then
		inputs_scan("up", up_input_id)
		return
	end

	if key.KeyCode == Enum.KeyCode.A and requesting_stratagem then
		inputs_scan("left", left_input_id)
		return
	end

	if key.KeyCode == Enum.KeyCode.S and requesting_stratagem then
		inputs_scan("down", down_input_id)
		return
	end

	if key.KeyCode == Enum.KeyCode.D and requesting_stratagem then
		inputs_scan("right", right_input_id)
		return
	end

	if key.KeyCode == Enum.KeyCode.LeftControl then
		local toggle_ui_sound = sounds:WaitForChild("toggle_ui")
		stratagem_ui.Enabled = not stratagem_ui.Enabled
		requesting_stratagem = not requesting_stratagem
		next_input_step = 1
		
		toggle_ui_sound:Play()
	end
end)

You should check “requesting_stratagem” too probably? Pretty sure WASD would always return true for processed (I’m not 100% sure about that here, I never actually tried).

You’ll probably want to also override the player controls in a local script (this is from a VERY old game so I don’t know if it’s still accurate)

local playerControls = require(Player.PlayerScripts.PlayerModule):GetControls()
playerControls:Enable(false) -- Don't know if there is a :Disable(), you can likely see
playerControls:Enable()
1 Like

Yeah i had the requesting_stratagem boolean in the conditionals, pretty sure it got caught up in the code i deleted, ima add it back.
The character control disabling might work, ill see if it still allows for input to be detected on wasd, which it should.

Ill lyk in the morning since its night for me lol

Is this smth like what you’re looking for? I could explain how I made it if so.

The first input sequence is me showing what happens when I do the inputs successfully, the others are me showing what happens when you do the wrong inputs.

EDIT: The updated example is below.

1 Like

Yeah this is pretty much what im trying to make, thatd be awesome if i could explain. one question tho, when the input text boxes turn red is that when a mis-input happens or when it’s successfully typed in?

Our end goal: To add a strategem, all we’d want to do is add data about it into a table we’ll store in a modulescript.

How do we get there?
Our strategem system, without the UI, is going to do 3 things:
#1 - Track inputs only when we’re holding down the keybind for calling a strategem.
#2 - Narrow down the list of possible strategems based on our input sequence.
#3 - After each input, check if we’ve finished a strategem and tell the server that we did so.
#4 - Reset our input sequence every time we fail all possible sequences or stop holding the call keybind.

For tracking inputs, possible strategems our input sequence could complete, what strategems we have available in total, and if we’re pressing the “call” keybind, we’ll use three tables and one variable.

local callingStrategem = false
local equippedStrategems = {  -- We'll keep this untouched after adding stuff into it.
     ["Strategem1"] = {"5", "7", "5", "3", "3"},
     ["Strategem2"] = {"5", "7", "9", "9"},
     ["Strategem3"] = {"5", "7", "5", "3", "1", "1", "1", "1"},
} -- Different lengths but same starting numbers for testing purposes.
local active = table.clone(equippedStrategems) -- Narrowing down purposes for later.
local inputSequence = {}

Every time you reset your inputSequence, reset active strategems to table.clone(equippedStrategems). Think of active as our table which will contain possible strategems that our sequence could lead to. Like, if our sequence is 5, 7, 5, then only Strategem1 and Strategem3 would remain in our active table.

Whenever you use userinputservice for a keyboard input (which you can check for with the UserInputType field of the input parameter of inputBegan/inputEnded), check if callingStrategem is true or not.

If it is true, check the input by adding it to your inputSequence table, and cycle through the table. For each iteration of this loop, loop through every strategem input map, checking the input against the value in the input map (you can do this by using the index of the i v pairs loop to reference the number we’re looking for in each input map).

If not, check if your input is your callStrategem keybind like Space or T.

-- This is what your code in UserInputService.InputBegan might look like.
if input.UserInputType == Enum.UserInputType.Keyboard then
	if callingStrategem then
        table.insert(inputSequence, input) -- "input" is the parameter from inputBegan
		for i, v in ipairs(inputSequence) do
            local successfulInput = true -- We'll check if our input matched a strategem.
            for strategem, inputMap in pairs(active) do
                 if v ~= inputMap[i] then -- Input didn't match >:(
                     active[strategem] = nil -- Remove strategem from possible options.
                     successfulInput = false
                     break
                 end
            end
            
            if successfulInput == true then
                 if #inputSequence == #inputMap then -- Means we finished a strategem.
                     print("Requesting: "..strategem..".")
                     active = table.clone(equippedStrategems)
                     inputSequence = {}
                 end
            else
                --- We're counting if any strats are still possible.
                 local strategemsRemaining = 0
                 for _, v in pairs(active) do
                     strategemsRemaining += 1 
                 end

                 if strategemsRemaining == 0 then
                     inputSequence = {} -- Reset inputSequence and possible strats.
                     active = table.clone(equippedStrategems) 
                 end
            end
        end
	elseif input.KeyCode.Name == settings.CallKeybind then
		callingStrategem = true -- Track that player is holding key down.
	end
end

-- In your inputEnded connection, just check for if the callkeybind isn't being held anymore.
-- If it isn't being held anymore, set callingStrategem = false.

Once a strategem is successfully called, you’ll fire a remote event to the server.

As for UI, I have a function (called initializeUI) that looks in our equippedStrategems table and clones a template strategem UI I have for each of them, adding as many input boxes (the grey/white boxes in my video example) as there are inputs in the strategem’s input map, and naming the cloned template to the name of our strategem that we’re currently dealing with.

My UI is basically set up like this: StrategemHUD (ScreenGUI) is the parent of StrategemHandler (our local script), Interface (which is where all our strategem template clones will be parented), and strategemTemplate (the template, which is a frame, that I’ll clone and use repeatedly).

I also have a function that, once I detect an input has been unsuccessful for a strategem, resets that strategem’s UI progress by changing all the little input boxes back to grey color. I have a parameter in it that lets me instead highlight the next input if an input was successful. This function was called updateUI by me.

Finally, I have a function called resetUI that just resets all UI back to normal.

For the whole “highlight next input” thing, when setting up the template clone for each strategem and duplicating the textLabels that are the grey/white little ‘input boxes’ you see in the example video, I make sure that the textLabel currently being cloned and being customized has its name set to “Input”..Index. Index is the current index of the table you’re on in your i v pairs loop while looping through. This lets me number and organize the input boxes, whose LayoutOrder I also edited so they’d be in order since I have a UIListLayout in my template for organizing them.

Since it’s kind of tedious to explain, here’s the code that I did, though it won’t work for your code since I added stuff to it for modularity and for avoiding hardcoding.

local function refreshUI()
	--print(equippedStrategems)
	for strategem, inputMap in pairs(equippedStrategems) do
		--print("Testing")
		local clone = template:Clone()
		local inputTemplate = clone.Inputs.Input
		clone.Parent = interface
		
		-- Set up the template.
		clone.Name = strategem
		clone.Visible = true
		clone.Icon.Image = strategemInfo[strategem].Icon
		--clone.Icon.Visible = false -- [DEBUGGING PURPOSES!!!!!!!!!!!!!!!]
		
		-- Set up each input.
		
		for i, v in ipairs(inputMap) do
			--print(inputMap)
			local clonedInput = inputTemplate:Clone()
			clonedInput.Parent = clone.Inputs
			clonedInput.Text = v
			clonedInput.TextColor3 = settings.Failure.Text
			clonedInput.BackgroundColor3 = settings.Failure.Background
			clonedInput.LayoutOrder = i
			clonedInput.Name = "Input"..i
		end
		
		inputTemplate:Destroy()
	end
end

When updating UI, I just check for the frame that has the strategem’s name in my UI since I renamed it during the cloning process, and then go into the Inputs and look for the input matching my current strategem progress (which I can find with #inputSequence) plus one since we’re trying to highlight the next input.

I’m having trouble articulating what I did but I hope this helped give you some sort of guidance on how to structure your code to make a strategem system.

1 Like

Hey, I edited my response above to try and explain what I did to make this updated strategem version I have linked here.

this is awesome!! thank you so much for taking some time out to help, i really apreciate this :folded_hands:

1 Like

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