Prompt Module help

Out of boredom, I’ve made a proximity prompt module (with the help of ai) since the default one can’t handle multiple keybinds.

Module
local CustomPrompt = {}

local UserInputService = game:GetService("UserInputService")
local ProximityPromptService = game:GetService("ProximityPromptService")
local TweenService = game:GetService("TweenService")
local TextService = game:GetService("TextService")
local Players = game:GetService("Players")

local LocalPlayer = Players.LocalPlayer

local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")

local GUISurfaceTemplate = script:WaitForChild("SurfaceGui")
local GUIBillboardTemplate = script:WaitForChild("BillboardGui")
local PROMPTTemplate = script:WaitForChild("PromptBase")

local KeyboardButtonImage = {
	[Enum.KeyCode.Backspace] = "rbxasset://textures/ui/Controls/backspace.png",
	[Enum.KeyCode.Return] = "rbxasset://textures/ui/Controls/return.png",
	[Enum.KeyCode.LeftShift] = "rbxasset://textures/ui/Controls/shift.png",
	[Enum.KeyCode.RightShift] = "rbxasset://textures/ui/Controls/shift.png",
	[Enum.KeyCode.Tab] = "rbxasset://textures/ui/Controls/tab.png",
}

local KeyboardButtonIconMapping = {
	["'"] = "rbxasset://textures/ui/Controls/apostrophe.png",
	[","] = "rbxasset://textures/ui/Controls/comma.png",
	["`"] = "rbxasset://textures/ui/Controls/graveaccent.png",
	["."] = "rbxasset://textures/ui/Controls/period.png",
	[" "] = "rbxasset://textures/ui/Controls/spacebar.png",
}

local KeyCodeToTextMapping = {
	[Enum.KeyCode.LeftControl] = "Ctrl",
	[Enum.KeyCode.RightControl] = "Ctrl",
	[Enum.KeyCode.LeftAlt] = "Alt",
	[Enum.KeyCode.RightAlt] = "Alt",
	[Enum.KeyCode.F1] = "F1",
	[Enum.KeyCode.F2] = "F2",
	[Enum.KeyCode.F3] = "F3",
	[Enum.KeyCode.F4] = "F4",
	[Enum.KeyCode.F5] = "F5",
	[Enum.KeyCode.F6] = "F6",
	[Enum.KeyCode.F7] = "F7",
	[Enum.KeyCode.F8] = "F8",
	[Enum.KeyCode.F9] = "F9",
	[Enum.KeyCode.F10] = "F10",
	[Enum.KeyCode.F11] = "F11",
	[Enum.KeyCode.F12] = "F12",
}

local function getScreenGui()
	local screenGui = PlayerGui:FindFirstChild("ProximityPrompts")
	if screenGui == nil then
		screenGui = Instance.new("ScreenGui")
		screenGui.Name = "ProximityPrompts"
		screenGui.ResetOnSpawn = false
		screenGui.Parent = PlayerGui
	end
	return screenGui
end

function CustomPrompt:CreateSurfacePrompt(Part, objectText, keybinds, onKeyPressed)

	local totalKeybinds = keybinds

	--// Tweens
	local tweensForFadeOut = {}
	local tweensForFadeIn = {}

	--// Tween Infos
	local tweenInfoFast = TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
	local tweenInfoQuick = TweenInfo.new(0.06, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)

	--// Create Surface Gui
	local promptUI = GUISurfaceTemplate:Clone()
	promptUI.Name = "Prompt"
	promptUI.Adornee = Part
	promptUI.Face = Enum.NormalId.Front
	promptUI.Parent = getScreenGui()

	--// Surface Gui parts
	local frame = promptUI:WaitForChild("Main")
	local line = frame:WaitForChild("Line")
	local objectTextLabel = frame:WaitForChild("ObjectText")
	local promptListFrame = frame:WaitForChild("Prompts")

	--// Object Text
	objectTextLabel.Text = objectText
	objectTextLabel.TextTransparency = 1

	table.insert(tweensForFadeIn, TweenService:Create(objectTextLabel, tweenInfoFast, { TextTransparency = 0 }))
	table.insert(tweensForFadeOut, TweenService:Create(objectTextLabel, tweenInfoFast, { TextTransparency = 1 }))

	--//  Line Separator
	line.Size = UDim2.fromScale(0, 0.02)
	table.insert(tweensForFadeIn, TweenService:Create(line, tweenInfoFast, { Size = UDim2.fromScale(1, 0.02) }))
	table.insert(tweensForFadeOut, TweenService:Create(line, tweenInfoFast, { Size = UDim2.fromScale(0, 0.02) }))

	--// Prompt List Frame
	promptListFrame.GroupTransparency = 1
	table.insert(tweensForFadeIn, TweenService:Create(promptListFrame, tweenInfoFast, { GroupTransparency = 0 }))
	table.insert(tweensForFadeOut, TweenService:Create(promptListFrame, tweenInfoFast, { GroupTransparency = 1 }))

	--// Connections and state
	local holdBeganConnection
	local holdEndedConnection
	local triggeredConnection
	local currentlyHeldKey = nil

	local keybindMap = {} -- stores data per key

	--// Create Prompts
	for _, bind in ipairs(totalKeybinds) do
		local key = bind.Key
		local action = bind.Interaction
		local duration = bind.Duration	

		--// Duplicate Prompt
		local promptFrame = PROMPTTemplate:Clone()
		promptFrame.Parent = promptListFrame

		table.insert(tweensForFadeIn, TweenService:Create(promptFrame, tweenInfoFast, { GroupTransparency = 0 }))
		table.insert(tweensForFadeOut, TweenService:Create(promptFrame, tweenInfoFast, { GroupTransparency = 1 }))

		--// Prompt Parts
		local keycodeFrame = promptFrame:WaitForChild("Keycode")
		local actionTextLabel = promptFrame:WaitForChild("Action")
		local fillFrame = keycodeFrame:WaitForChild("Fill")
		
		local keycodeScale = keycodeFrame:WaitForChild("UIScale")
		
		local keycodeKeybindText = keycodeFrame:WaitForChild("TextLabel")
		local keycodeKeybindImage = keycodeFrame:WaitForChild("ImageLabel")

		--// Tweens
		local tweenHoldScaleBegin = TweenService:Create(keycodeScale, tweenInfoFast, { Scale = 1.1 })
		local tweenHoldScaleEnd = TweenService:Create(keycodeScale, tweenInfoFast, { Scale = 1 })
		local tweenFillBegin = TweenService:Create(fillFrame, TweenInfo.new(duration), { Size = UDim2.fromScale(1, 1) })
		local tweenFillEnd = TweenService:Create(fillFrame, tweenInfoFast, { Size = UDim2.fromScale(1, 0) })

		-- Set text
		actionTextLabel.Text = action
		
		local buttonTextString = UserInputService:GetStringForKeyCode(key)
		local buttonTextImage = KeyboardButtonImage[key]
		
		if buttonTextImage == nil then
			buttonTextImage = KeyboardButtonIconMapping[buttonTextString]
		end

		if buttonTextImage == nil then
			local keyCodeMappedText = KeyCodeToTextMapping[key]
			if keyCodeMappedText then
				buttonTextString = keyCodeMappedText
			end
		end
		
		if buttonTextImage then
			keycodeKeybindImage.Image = buttonTextImage
		elseif buttonTextString ~= nil and buttonTextString ~= "" then
			keycodeKeybindText.Text = buttonTextString
		else
			error(
				"ProximityPrompt "
					.. " has an unsupported keycode for rendering UI: "
					.. tostring(key)
			)
		end

		-- Store all needed info in the keybindMap
		keybindMap[key] = {
			ScaleUp = tweenHoldScaleBegin,
			ScaleDown = tweenHoldScaleEnd,
			FillUp = tweenFillBegin,
			FillDown = tweenFillEnd,
			Duration = duration,
			Action = action
		}
	end

	--// Input Handling
	holdBeganConnection = UserInputService.InputBegan:Connect(function(input, gp)
		
		--// Ignore if typing in chat
		if UserInputService:GetFocusedTextBox() then return end
		if input.UserInputType ~= Enum.UserInputType.Keyboard then return end
		if currentlyHeldKey then return end

		local data = keybindMap[input.KeyCode]
		
		if data and data.Duration > 0 then

			currentlyHeldKey = input.KeyCode
			data.ScaleUp:Play()
			data.FillUp:Play()

			triggeredConnection = data.FillUp.Completed:Connect(function()
				data.ScaleDown:Play()
				data.FillDown:Play()

				if onKeyPressed then
					onKeyPressed(data.Action)
				end

				if triggeredConnection then
					triggeredConnection:Disconnect()
					triggeredConnection = nil
				end

				currentlyHeldKey = nil
			end)	
		end
	end)

	holdEndedConnection = UserInputService.InputEnded:Connect(function(input, gp)
		if input.UserInputType ~= Enum.UserInputType.Keyboard then return end

		local data = keybindMap[input.KeyCode]
		if data and currentlyHeldKey == input.KeyCode then
			data.ScaleDown:Play()
			data.FillDown:Play()

			if triggeredConnection then
				triggeredConnection:Disconnect()
				triggeredConnection = nil
			end

			currentlyHeldKey = nil
		end
	end)

	--// Fade In
	for _, tween in ipairs(tweensForFadeIn) do
		tween:Play()
	end

	--// Cleanup method
	function CustomPrompt:Cleanup()
		if holdBeganConnection then holdBeganConnection:Disconnect() end
		if holdEndedConnection then holdEndedConnection:Disconnect() end
		if triggeredConnection then triggeredConnection:Disconnect() end

		for _, tween in ipairs(tweensForFadeOut) do
			tween:Play()
		end

		task.wait(0.25)
		promptUI:Destroy()
	end
end

return CustomPrompt

(not done yet)

While testing, I realized that I want it to be called by the server just like the default prompt since I want each prompt to run a specific code, but since the module used LocalPlayer, it only works when being called by the client. I tried to make an approach using Remote Events but due low amount of braincells left, I dont now how I would do that.

Heres the code that works for Local Script

Local Script
local Players = game:GetService("Players")
local ProximityPromptService = game:GetService("ProximityPromptService")
local CustomPrompt = require(game:GetService("ReplicatedStorage").CustomPrompt)

ProximityPromptService.PromptShown:Connect(function(prompt, inputType)
	
	if prompt.Style == Enum.ProximityPromptStyle.Default then return end
	
	local keybinds = {
		{ Key = Enum.KeyCode.Q, Interaction = "action 1", Duration = 0.1 },
		{ Key = Enum.KeyCode.E, Interaction = "action 2", Duration = 0.5 },
		{ Key = Enum.KeyCode.F, Interaction = "action 3", Duration = 1 }
	}
	
	local objectText = prompt.ObjectText
	
	CustomPrompt:CreateSurfacePrompt(prompt.Parent, objectText , keybinds, function(key)
		if key == "action 1" then
			print("action 1")
		elseif key =="action 2" then
			print("action 2")
		else
			print("action 3")
		end
	end)
	
	local hiddenConn
	hiddenConn = prompt.PromptHidden:Connect(function()
		
		CustomPrompt:Cleanup()
		
		if hiddenConn then
			hiddenConn:Disconnect()
			hiddenConn = nil
		end
	end)
end)

What I’m thinking is when a prompt is shown, the remote event is fired, received by a local script, and that local script handles the module. Same for when the prompt is hidden. I also realized just now that I dont know how the server would handle the input began

note: I know its kind of messy. Thanks for the help

Fire remote event from client to server.
On server validate conditions and distance of player from proximity prompt ACORDING TO DATA THAT YOU HAVE ON SERVER AND NOT USER INPUT*
Should work fine.
If you want more abstraction uhhhh…uhhhh…“.disapears

1 Like

Custom prompt and input should run on the client. The server should only tell the client when to show the prompt and when to hide the prompt from the player. Also in your CreateSurfacePrompt, you should clean up all event connections when the prompt is hiden from the player. Reckon you could add a helper if you plan on expanding your custom prompt more

I changed things out, decided to use a local script instead of a module and remote event to send signals when it’s triggered, much easier than module :sob:

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