--[[ Created by Maelstorm_1973 Copyright (C)2023 Dracolynxis Technologies Inc. Library Scripts Context Action Service This replaces Roblox's ContextActionService system which is limited to 7 events. The number of events for this is unlimited. This uses lookup tables to determine what events have handlers attached to them, so this is quite fast in processing events and getting them to their handlers. ******** Functions/Methods **** bindAction Binds one or more input events to an action. bindAction(actionName, bindFunc, priority, touchButton, keyList) actionName Name of the action (Required). bindFunc Reference to the callback function (Required). priority Enum.ContextActionPriority or nil (default is Enum.ContextActionPriority.Medium). touchButton If a table, then it will create a touchbutton on the screen according to the parameters in the table. See below for table format. keyList Can be a either a single Enum.KeyCode value or a table of Enum.KeyCode values, or nil. Notes: One of touchButton or keyList is required. Table format for the touchButton parameter of bindAction: { Position UDim2 GUI position for touchscreen button. Size Size in Pixles of the touchscreen button. Image A Roblox asset Id --> "rbxassetid://"; Text Renders a text button with the string. BaseImage If the button base image and/or surroundng ring is to be rendered. false or enums.CASImageCode.Both - Do not render either background image or backround ring. enums.CASImageCode.Background - Do not render the background image. enums.CASImageCode.Ring - Do not render the background ring. BaseImageColor Colors the background ring only. Touch If false, then this button is not rendered. } The requirements for the callback function has the same requirements as the callback function for Roblox's ContextActionService. The function definition is as follows: local function callback(actionName, inputState, inputObject) If you wish to use a grid for the icons, then the utility function setGridPosition is provided. If you wish to use a standardized size of your onscreen touch icons, then the setIconSize utility function is provided. **** bindActionMultiple Binds multiple actions in a single function call. bindActionMultiple(buttonList) buttonList A table of tables which contains all the information that is needed to bind actions. Table format for the buttonList parameter of bindActionMultiple: { Action1 = {}; Action2 = {}; Action3 = {}; ... } Action1, Action2, and Action3 are subtables that have the same format as the one used for the touchButton parameter of bindAction above, with addtions. The names of the actions (Action1, Action2, Action3, etc...) does not matter. You can name them whatever makes sense to you. The additions for the action table format are as follows: { ActionName The name of the action to bind. (Required) Callback The function reference to call when a corresponding input event occurs. (Required) Priority If nil or not specified, then the default priority is Medium. KeyBind The name of the previously bound action. Will not throw an error or warning if the action does not exist. **** unbindActionAll Unbinds all previously bound actions. unbindActionAll() **** getAllBindings Returns a table of all current bindings. getAllBinding(): Table The table that is returned is an array of tables with the following information: { ActionName The name of the previously bound action. KeyBind A table of the keybindings for this action. Touch True if a touch button is associated with this action. False if not. } **** hideTouchButtons Shows or hides the touch buttons. hideTouchButtons(state) state If true, then the touch screen buttons are hidden from view. If false, then the touch screen buttons are visible. **** setGridPosition Sets the position of a touch button according to a grid layout. setGridPosition(x, y, horiz, vert): UDim2 x Horizontal grid position offset (not pixles). y Vertical grid position offset (not pixles). horiz Horizontal screen region reference indicator. 0 is from the left edge of the screen. If x = 0, then the left half of the touch button will be displayed off screen. Positive x values will move the touch button to the right. 0.5 is from the center of the screen. If x = 0, then the touch button is rendered in the middle of the screen. Positive x values move the touch button to the right while negative x values move the touch button to the left. 1 is from the right edge of the screen. As with 0 above, if x = 0, then right half of the touch button will be displayed off screen. Negative x values will move the touch button to the left. vert Vertical screen retion reference indicator. Similar to the horiz and x values above, this is for the y value. Note that the GUI Inset of 36 pixles is ignored for vertical placements. 0 is from the top edge of the screen. If y = 0, then the top half of the touch button will be displayed off screen. Positive y values will move the touch button down. 0.5 is from the center of the screen. If y = 0, then the touch button is rendered in the middle of the screen. Positive y values will move the touch button down while negative y values will move the touch button up. 1 is from the bottom edge of the screen. If y = 0 then the bottom half of the touch button will be rendered off screen. Negative y values will move the touch button up. The default values for horiz is 1 and for vert it's 0.5. This means that touch buttons that use these values for horiz and vert will have their positional reference at the middle right edge of the screen. The default grid values are 60x60 pixles as defined in scaleFactorX and scaleFactorY. **** setIconSize Returns the size of an icon from the given size. setIconSize(size): Number size Size scale for the icon (not pixles). The size is a multiple of the icon scale size which is currently 25 pixles as defined in scaleFactorS. ******** Enumerations This module utilizes custom enumerations called enums which is just a dictionary of name = value pairs. The definition of which is below: local enums = { -- Enumeration: CAS Image Frame Codes CASImageCode = { Both = 0; -- Remove both backround image and ring. Background = 1; -- Remove background only. Ring = 2; -- Remove ring only. }; } The module has included with the require the enum definitions for your convenience. To utilize these definitions: local moduleName = require(ContextActionService) local both = moduleName.enums.CASImageCode.Both ******** Keybinds There are some non-standard keybinds when dealing with the mouse and gamepads. These keybinds use the World codes from Enum.KeyCode. These codes are part of Roblox's API but my research has revealed that nothing uses them. Those definitions are as follows: Enum.KeyCode.World80 Mouse Wheel Forward Enum.KeyCode.World81 Mouse Wheel Backward Enum.KeyCode.World91 Mouse Wheel Moved Enum.Keycode.World92 Mouse Movement Enum.Keycode.World93 Mouse Button 1 Enum.Keycode.World94 Mouse Button 2 Enum.Keycode.World95 Mouse Button 3 Binding these keycodes will fire the defined callback functions for these actions. NOTE: As of 14 November 2023, I have just acquired an X-Box gamepad, so expect updates as I figure out how to code for it. --]] -- ******** Requirements -- Required Game Services and Facilities local playerService = game:GetService("Players") local userInputService = game:GetService("UserInputService") local starterGui = game:GetService("StarterGui") -- Scripting Support local localPlayer = playerService.LocalPlayer -- ******** Local Data local enums = { -- Enumeration: CAS Image Frame Codes CASImageCode = { Both = 0; -- Remove both backround image and ring. Background = 1; -- Remove background only. Ring = 2; -- Remove ring only. }; } local playerGui = localPlayer.PlayerGui local clientGui = nil local CASGuiName = "CASGui" local buttonFrame = nil local buttonImage = nil local buttonText = nil local moduleReady = false local mutex = false local humanDiedEvent1 = nil local humanDiedEvent2 = nil local humanDiedEvent3 = nil local diedDebounce = false local handlerIndex = 0 local DEBUG = true local INPUT_TEST_MODE = false local scaleFactorX = 60 -- X axis coordinate scale local scaleFactorY = 60 -- Y axis coordinate scale local scaleFactorS = 25 -- Size scale factor -- Input Type Classification -- 0 Touch -- 1 Mouse -- 2 Keyboard -- 3 Gamepad -- Everything else is undefined. local inputTypeClass = { [Enum.UserInputType.Touch] = 0; [Enum.UserInputType.MouseMovement] = 1; [Enum.UserInputType.MouseButton1] = 1; [Enum.UserInputType.MouseButton2] = 1; [Enum.UserInputType.MouseButton3] = 1; [Enum.UserInputType.MouseWheel] = 1; [Enum.UserInputType.Keyboard] = 2; [Enum.UserInputType.Gamepad1] = 3; [Enum.UserInputType.Gamepad2] = 3; [Enum.UserInputType.Gamepad3] = 3; [Enum.UserInputType.Gamepad4] = 3; [Enum.UserInputType.Gamepad5] = 3; [Enum.UserInputType.Gamepad6] = 3; [Enum.UserInputType.Gamepad7] = 3; [Enum.UserInputType.Gamepad8] = 3; } -- Function handler list. local functionList = {} -- Lookup by action name. local nameList = {} -- Lookup by keybindings. local bindList = {} -- Template for priority actions. local callbackTemplate = { priority = Enum.ContextActionPriority.Medium; callfunc = nil; } local actionTemplate = { name = ""; callback = { high = {}; medium = {}; low = {}; }; touch = { frame = { instance = nil; }; buttonText = nil; buttonImage = nil; }; bindings = nil; } -- ******** Functions/Methods: Utility -- Recursively copies a table into a new table. local function recursiveCopy(dataTable) local tableCopy = {} for index, value in pairs(dataTable) do if type(value) == "table" then value = recursiveCopy(value) end tableCopy[index] = value end return tableCopy end -- Returns the player's parts. local function getPlayerParts(player) local char = player.Character while char == nil do task.wait(0.1) char = player.Character end local human = char:WaitForChild("Humanoid") local root = char:WaitForChild("HumanoidRootPart") return char, human, root end -- Waits for as long as necessary for the module -- initialization routine to complete. local function waitModule() while moduleReady == false do task.wait() end end -- Locks the mutex for mutual exclusion. local function lockMutex() local flag = true while flag == true do while mutex == true do task.wait(0.1) end if mutex == false then mutex = true flag = false end end end -- Unlocks the mutial exclusion mutex. local function unlockMutex() mutex = false end -- ******** Functions/Methods: Function Handles -- Adds the specified function and associated data to -- the function handler list. Returns the handle. local function addFunctionHandle(func, action, priority) local handle = handlerIndex handlerIndex += 1 local data = { func = func; action = action; priority = priority; handle = handle; } functionList[handle] = data return handle end -- Removes the specified handle and associated function -- from the internal data. local function removeFunctionHandle(handle) local hdata = functionList[handle] if hdata == nil then return end -- Retrieve Data, if possible... lockMutex() local adata = nameList[hdata.action] local plist = nil if hdata.priority == Enum.ContextActionPriority.High then plist = adata.callback.high elseif hdata.priority == Enum.ContextActionPriority.Medium then plist = adata.callback.medium elseif hdata.priority == Enum.ContextActionPriority.Low then plist = adata.callback.low else unlockMutex() return end -- Search the list for the function. If found, then -- remove it. for i = 1, #plist, 1 do if plist[i] == hdata.func then table.remove(plist, i) functionList[handle] = nil break end end unlockMutex() end -- ******** Functions/Methods: Common Event Handlers -- Common event code to get the data, if it exists. Also checks -- if a keyboard is enabled and if there is a text box that has -- focus. If it does, then we return nil because the user is -- most likely typing something in the chat. local function eventCommonDetermine(object, filter) -- Looks for GUI objects at the given location. local function guiObjectCheck(object) local list = playerGui:GetGuiObjectsAtPosition(object.Position.X, object.Position.Y) local name = nil for _, item in pairs(list) do if item:IsA("Frame") then name = item:GetAttribute("Action") if name ~= nil then break end end end if name == nil then return nil end return nameList[name] end -- Touch Input Handler local function inputHandlerTouch(object) if userInputService.TouchEnabled == true then if object.UserInputType == Enum.UserInputType.Touch then return guiObjectCheck(object) end else return nil end end -- Mouse Input Handler -- This converts mouse inputs to world key codes. -- The world key codes are no longer used per SDL 2.0. -- This is discussed in this post: -- https://devforum.roblox.com/t/what-are-the-inputs-world0-to-world95-used-for/222976 -- More information about SDL can be found here: -- https://wiki.libsdl.org/SDL2/FrontPage -- Since these are no longer used, we are reusing them for -- our purpose. Mouse wheel forward is Enum.KeyCode.World80 -- and mouse wheel backward is Enum.KeyCode.World81 -- We can alter the InputObject because this function -- is guaranteed to only be called when we have a mouse -- input event which means that there is no keycode -- assigned. local function inputHandlerMouse(object) if userInputService.MouseEnabled == true then local keyCode = Enum.KeyCode.Unknown local result = nil -- Go through all the possible mouse states and -- assign key codes for each one. if object.UserInputType == Enum.UserInputType.MouseMovement then keyCode = Enum.KeyCode.World92 object.KeyCode = keyCode result = bindList[keyCode] elseif object.UserInputType == Enum.UserInputType.MouseButton1 then keyCode = Enum.KeyCode.World93 object.KeyCode = keyCode result = guiObjectCheck(object) if result == nil then result = bindList[keyCode] end elseif object.UserInputType == Enum.UserInputType.MouseButton2 then keyCode = Enum.KeyCode.World94 object.KeyCode = keyCode result = guiObjectCheck(object) if result == nil then result = bindList[keyCode] end elseif object.UserInputType == Enum.UserInputType.MouseButton3 then keyCode = Enum.KeyCode.World95 object.KeyCode = keyCode result = guiObjectCheck(object) if result == nil then result = bindList[keyCode] end elseif object.UserInputType == Enum.UserInputType.MouseWheel then -- Mouse wheel is a special case since it can morph into -- different codes depending on what has been bound. keyCode = Enum.KeyCode.World91 object.KeyCode = keyCode result = bindList[keyCode] if result == nil then if object.Position.Z == 1 then -- Mouse Wheel Forward keyCode = Enum.KeyCode.World80 object.KeyCode = keyCode result = bindList[keyCode] elseif object.Position.Z == -1 then -- Mouse Wheel Backward keyCode = Enum.KeyCode.World81 object.KeyCode = keyCode result = bindList[keyCode] else warn("Invalid psoition value received for mouse wheel.") return nil end end end return result else return nil end end -- Keyboard Input Handler local function inputHandlerKeyboard(object) if userInputService.KeyboardEnabled == true then local keyCode = Enum.KeyCode.Unknown if userInputService:GetFocusedTextBox() == nil then if object.KeyCode ~= Enum.KeyCode.Unknown then keyCode = object.KeyCode end end return bindList[keyCode] else return nil end end -- Gamepad Input Handler local function inputHandlerGamepad(object) if userInputService.GamepadEnabled == true then local keyCode = Enum.KeyCode.Unknown if object.KeyCode ~= Enum.KeyCode.Unknown then keyCode = object.KeyCode end return bindList[keyCode] else return nil end end -- Make sure that the state is the correct one. if object.UserInputState ~= filter then return nil end -- Setup local keyCode = Enum.KeyCode.Unknown local data = nil -- Classify the input type broadly. local inputType = inputTypeClass[object.UserInputType] if inputType == nil then -- If the input type is not in the table, then -- we ignore it. return nil end -- Process input according to class. if inputType == 0 then -- Touch Input return inputHandlerTouch(object) elseif inputType == 1 then -- Mouse Input return inputHandlerMouse(object) elseif inputType == 2 then -- Keyboard Input return inputHandlerKeyboard(object) elseif inputType == 3 then -- Gamepad Input return inputHandlerGamepad(object) end -- We shouldn't get here, but if we do then -- return nil return nil end -- Runs a function call list. local function eventCommonRunList(list, object, data) local name = data.name for _, item in ipairs(list) do unlockMutex() local status, message = pcall(function() return item(name, object.UserInputState, object) end) lockMutex() if status == false then warn("ContextActionService Callback Error:", message) warn(name, item) print(debug.traceback()) return false end if message ~= Enum.ContextActionResult.Pass then return false end end return true end -- Runs the priority context action lists. local function eventCommonPriority(object, data) local result result = eventCommonRunList(data.callback.high, object, data) if result == false then return end result = eventCommonRunList(data.callback.medium, object, data) if result == false then return end result = eventCommonRunList(data.callback.low, object, data) if result == false then return end end -- Common event handler for input beginning. local function eventCommonBegin(object) lockMutex() local data = eventCommonDetermine(object, Enum.UserInputState.Begin) if data == nil then unlockMutex() return end eventCommonPriority(object, data) unlockMutex() end -- Common event handler for input changing. local function eventCommonChange(object) lockMutex() local data = eventCommonDetermine(object, Enum.UserInputState.Change) if data == nil then unlockMutex() return end eventCommonPriority(object, data) unlockMutex() end -- Common event handler for input ending. local function eventCommonEnd(object) lockMutex() local data = eventCommonDetermine(object, Enum.UserInputState.End) if data == nil then unlockMutex() return end eventCommonPriority(object, data) unlockMutex() end -- ******** Functions/Methods: Main -- Sets up the touch button frame. local function setButtonFrame(data) local frame = buttonFrame:Clone() data.touch.frame.instance = frame frame:SetAttribute("Action", data.name) frame.Name = data.name frame.Parent = playerGui:FindFirstChild(CASGuiName) return frame end -- Checks the callbacks. local function checkCallbacks(list, func) for _, item in pairs(list) do if item == func then return true end end return false end -- Sets up the data for the action to be bound. local function bindActionSetup(name, bindFunc) local data local control if nameList[name] ~= nil then data = nameList[name] control = true -- Check if the callback function was already registered. local result = false result = if checkCallbacks(data.callback.high, bindFunc) == true then true else result result = if checkCallbacks(data.callback.medium, bindFunc) == true then true else result result = if checkCallbacks(data.callback.low, bindFunc) == true then true else result if result == true then warn("funcBind function was already registered for this action:", name) print(debug.traceback()) return nil end else data = recursiveCopy(actionTemplate) data.name = name nameList[name] = data control = false end return data, control end -- Adds the callback function to the list with -- the specified priority. local function bindActionCallback(data, bindFunc, priority) if priority == nil then priority = Enum.ContextActionPriority.Medium end if priority == Enum.ContextActionPriority.High then table.insert(data.callback.high, bindFunc) elseif priority == Enum.ContextActionPriority.Medium then table.insert(data.callback.medium, bindFunc) elseif priority == Enum.ContextActionPriority.Low then table.insert(data.callback.low, bindFunc) else warn("Invalid priority specified. Must be of Enum.ContextActionPriority or nil.") return false end return addFunctionHandle(bindFunc, data.name, priority) end -- Handles the bind action touchscreen button. local function bindActionTouchscreen(data, button) if button == true then waitModule() setButtonFrame(data) elseif type(button) == "table" then -- If the touch flag is false, then we do not -- create a touch button. Exit. if button.Touch == false then return end waitModule() local frame = setButtonFrame(data) if button.Position ~= nil then frame.Position = button.Position end if button.Size ~= nil then frame.Size = button.Size end if button.BaseImage ~= nil then local image if button.BaseImage == false or button.BaseImage == enums.CASImageCode.Both then image = frame:FindFirstChild("BaseImage") image:Destroy() image = frame:FindFirstChild("RingImage") image:Destroy() elseif button.BaseImage == enums.CASImageCode.Background then image = frame:FindFirstChild("BaseImage") image:Destroy() elseif button.BaseImage == enums.CASImageCode.Ring then image = frame:FindFirstChild("RingImage") image:Destroy() end end if button.BaseImageColor ~= nil then if typeof(button.BaseImageColor) == "BrickColor" then local image = frame:FindFirstChild("RingImage") if image ~= nil then image.ImageColor3 = button.BaseImageColor.Color end elseif typeof(button.BaseImageColor) == "Color3" then local image = frame:FindFirstChild("RingImage") if image ~= nil then image.ImageColor3 = button.BaseImageColor end end end if button.Image ~= nil then local image = buttonImage:Clone() image.Image = button.Image image.Parent = frame data.touch.buttonImage = image if button.ImageSizeFactor ~= nil then local x = image.Size.X.Offset + button.ImageSizeFactor local y = image.Size.Y.Offset + button.ImageSizeFactor image.Size = UDim2.new(1, x, 1, y) end end if button.Text ~= nil then local text = buttonText:Clone() text.Text = button.Text text.Parent = frame data.touch.buttonText = text if button.TextSizeFactor ~= nil then local x = text.Size.X.Offset + button.TextSizeFactor local y = text.Size.Y.Offset + button.TextSizeFactor text.Size = UDim2.new(1, x, 1, y) end end end end -- Handles the bind action for keyboard/gamepad buttons. local function bindActionKeyboard(data, keybindList) if keybindList ~= nil then if type(keybindList) == "table" then -- Multiple Enum.KeyCode in a list. data.bindings = recursiveCopy(keybindList) for _, keycode in pairs(keybindList) do bindList[keycode] = data end else -- Single Enum.KeyCode bindList[keybindList] = data data.bindings = { keybindList } end end end -- Binds one or more keycodes to an action. local function bindAction(actionName, bindFunc, priority, touchButton, keyList) lockMutex() -- Check Input if type(actionName) ~= "string" or actionName == "" then warn("The name parameter must be specified.") unlockMutex() return nil end if type(bindFunc) ~= "function" then warn("The bindFunc parameter must be specified.") unlockMutex() return nil end if touchButton == nil and keyList == nil then warn("One or more of of touchButton or bindList must be specified.") unlockMutex() return nil end -- Setup local data, control = bindActionSetup(actionName, bindFunc) if data == nil then unlockMutex() return nil end -- Bind Function local result = bindActionCallback(data, bindFunc, priority) if result == false then if control == false then nameList[actionName] = nil end unlockMutex() return nil end -- Set Bindings if DEBUG ~= true then -- Touchscreen if userInputService.TouchEnabled == true then bindActionTouchscreen(data, touchButton) end -- Keyboard/Gamepad if userInputService.KeyboardEnabled == true or userInputService.GamepadEnabled == true then bindActionKeyboard(data, keyList) end else bindActionTouchscreen(data, touchButton) bindActionKeyboard(data, keyList) end -- Unlock Mutex unlockMutex() -- Returns the function handle. return result end -- This is used if multiple buttons are to be created at -- once. Returns a table of handles. local function bindActionMultiple(buttonList) lockMutex() -- Check Input if type(buttonList) ~= "table" then warn("Input data must be a table.") unlockMutex() return nil end -- Setup local data local result local control local handleList = {} -- Run the loop for each button defined. for _, button in pairs(buttonList) do if type(button.ActionName) ~= "string" or button.ActionName == "" then warn("The button.ActionName parameter must be specified.") continue end if type(button.Callback) ~= "function" then warn("The button.Callback parameter must be specified and must be a function.") continue end if (button.Image == nil or button.Image == "") and (button.Text == nil or button.Text == "") and button.KeyBind == nil then warn("One or more of of touchButton or bindList must be specified.") continue end data, control = bindActionSetup(button.ActionName, button.Callback) if data == nil then continue end result = bindActionCallback(data, button.Callback, button.Priority) if result == false then if control == false then nameList[button.ActionName] = nil continue end end if DEBUG ~= true then if userInputService.TouchEnabled == true then bindActionTouchscreen(data, button) end if userInputService.KeyboardEnabled == true or userInputService.GamepadEnabled == true then bindActionKeyboard(data, button.KeyBind) end else bindActionTouchscreen(data, button) bindActionKeyboard(data, button.KeyBind) end table.insert(handleList, result) end -- Unlock Mutex unlockMutex() -- Returns the handle list. return handleList end -- Unbinds all actions with the given name. Does not throw -- an error if the action name is not defined. local function unbindAction(name) lockMutex() if name == nil or name == "" then warn("The name parameter must be specified.") unlockMutex() return end if nameList[name] == nil then unlockMutex() return end local data = nameList[name] if data.touch.frame.eventBegin ~= nil then data.touch.frame.eventBegin:Disconnect() data.touch.frame.eventBegin = nil end if data.touch.frame.eventChange ~= nil then data.touch.frame.eventChange:Disconnect() data.touch.frame.eventChange = nil end if data.touch.frame.eventEnd ~= nil then data.touch.frame.eventEnd:Disconnect() data.touch.frame.eventEnd = nil end if data.touch.frame.instance ~= nil then data.touch.frame.instance:Destroy() data.touch.frame.instance = nil end for _, key in pairs(data.bindings) do bindList[key] = nil end nameList[name] = nil -- Unlock Mutex unlockMutex() end -- Removes all bindings local function unbindActionAll() lockMutex() for name, data in pairs(nameList) do local data = nameList[name] if data.touch.frame.eventBegin ~= nil then data.touch.frame.eventBegin:Disconnect() data.touch.frame.eventBegin = nil end if data.touch.frame.eventChange ~= nil then data.touch.frame.eventChange:Disconnect() data.touch.frame.eventChange = nil end if data.touch.frame.eventEnd ~= nil then data.touch.frame.eventEnd:Disconnect() data.touch.frame.eventEnd = nil end if data.touch.frame.instance ~= nil then data.touch.frame.instance:Destroy() data.touch.frame.instance = nil end end nameList = {} bindList = {} unlockMutex() end -- Returns a table of all bindings. The table includes the -- name of the binding, the keycodes, and if there is a touch -- screen button. local function getAllBindings() lockMutex() local button local keys local tab = {} for name, data in pairs(nameList) do if data.touch.frame.instance ~= nil then button = true else button = false end keys = recursiveCopy(data.bindings) table.insert(tab, { ActionName = name; KeyBind = keys; Touch = button; }) end unlockMutex() return tab end -- Hides/Shows screen touchbuttons. local function hideTouchButtons(state) local clientGui = playerGui:FindFirstChild(CASGuiName) if clientGui ~= nil then if state == true then clientGui.Enabled = false elseif state == false or state == nil then clientGui.Enabled = true end end end -- Returns a UDim2 based on a grid position. local function setGridPosition(x, y, horiz, vert) -- Setup local hsc local vsc if horiz ~= nil then hsc = horiz else hsc = 1 end if vert ~= nil then vsc = vert else vsc = 0.5 end local pos = UDim2.new(hsc, x * scaleFactorX, vsc, y * scaleFactorY) return pos end -- Returns a UDim2 size. local function setIconSize(size) if size == nil then size = 1 end local final = size * scaleFactorS return UDim2.new(0, final, 0, final) end -- ******** Event Handlers -- Called when the player dies so we can reset everything. local function humanoidDied() if diedDebounce == false then diedDebounce = true unbindActionAll() -- Not an issue unless the respawn time is < 1 second. task.delay(1, function() diedDebounce = false end) end end -- Connects the death events so we can reset if the -- player dies. local function connectDeathEvents() if humanDiedEvent1 == nil then local char = localPlayer.Character while char == nil do task.wait() char = localPlayer.Character end local human = char:WaitForChild("Humanoid") humanDiedEvent1 = human.Died:Connect(function() humanoidDied() end) humanDiedEvent2 = human.StateChanged:Connect(function(old, new) if new == Enum.HumanoidStateType.Dead then humanoidDied() end end) humanDiedEvent3 = human.HealthChanged:Connect(function(health) if health <= 0 then humanoidDied() end end) end end -- ******** Events -- Called when a user input is started. userInputService.InputBegan:Connect(function(object, event) if INPUT_TEST_MODE == true then print("Input Begin:", { InputType = object.UserInputType; InputState = object.UserInputState; Position = object.Position; Delta = object.Delta; KeyCode = object.KeyCode; }) else eventCommonBegin(object) end end) -- Called when a user input is changed. userInputService.InputChanged:Connect(function(object, event) if INPUT_TEST_MODE == true then print("Input Change:", { InputType = object.UserInputType; InputState = object.UserInputState; Position = object.Position; Delta = object.Delta; KeyCode = object.KeyCode; }) else eventCommonChange(object) end end) -- Called when a user input has ended. userInputService.InputEnded:Connect(function(object, event) if INPUT_TEST_MODE == true then print("Input End", { InputType = object.UserInputType; InputState = object.UserInputState; Position = object.Position; Delta = object.Delta; KeyCode = object.KeyCode; }) else eventCommonEnd(object) end end) -- Clones the GUI from StarterGUI to PlayerGUI when the -- player's character loads, if needed. localPlayer.CharacterAdded:Connect(function(char) if playerGui:FindFirstChild(CASGuiName) == nil then local gui = starterGui:FindFirstChild(CASGuiName) local newGui = gui:Clone() newGui.Parent = playerGui clientGui = newGui end end) -- ******** Initialization -- We have to do some strange things with this because the -- module can be loaded in different LUA memory spaces. So -- the initialization will run for each seperate memory -- space the module is loaded in. We have to make sure that -- only one GUI is present in both StarterGui and PlayerGui. -- Creates the screen GUI for the module. Because this module -- can be included in different memory spaces, we need to check -- to make sure that there is only one GUI. local function createGui() -- Creates and returns a new ScreenGui with parameters set. local function create() local gui = Instance.new("ScreenGui") gui.Name = CASGuiName gui.IgnoreGuiInset = true gui.ResetOnSpawn = true gui.ZIndexBehavior = Enum.ZIndexBehavior.Global gui.DisplayOrder = 20 gui.Enabled = true return gui end -- Check to see if the GUI is in PlayerGui. If not, then -- we create it and place it there. There can be only -- one instance in PlayerGui. local clientGui = playerGui:FindFirstChild(CASGuiName) if clientGui == nil then clientGui = create() clientGui.Parent = playerGui end -- Checks to see if the GUI is in StarterGui. If not, -- then we clone clientGui into it. There can be only -- one instance in StarterGui. local sourceGui = starterGui:FindFirstChild(CASGuiName) if sourceGui == nil then sourceGui = clientGui:Clone() sourceGui.Parent = starterGui end end -- Creates the template for the basic touchscreen -- button. local function createTouchButtonFrame() -- Button Frame local frame = Instance.new("Frame") frame.Name = "Frame" frame.Active = true frame.AnchorPoint = Vector2.new(0.5, 0.5) frame.Position = UDim2.new(0.5, 0, 0.5, 0) frame.Size = UDim2.new(0, 35, 0, 35) frame.BackgroundTransparency = 1 frame.ZIndex = 16300 -- Image Button -- This image is always present unless it's specified -- to be deleted. local button = Instance.new("ImageButton") button.Name = "BaseImage" button.AnchorPoint = Vector2.new(0.5, 0.5) button.Active = false button.Position = UDim2.new(0.5, 0, 0.5, 0) button.Size = UDim2.new(1, 0, 1, 0) button.BackgroundTransparency = 1 button.BorderSizePixel = 0 button.ImageColor3 = BrickColor.new("Institutional white").Color button.Image = "rbxassetid://7185003058" button.ZIndex = 16301 button.Parent = frame -- This is overlaid on top of the previous image. -- This image is always present unless it's specified -- to be deleted. local button = Instance.new("ImageButton") button.Name = "RingImage" button.AnchorPoint = Vector2.new(0.5, 0.5) button.Active = false button.Position = UDim2.new(0.5, 0, 0.5, 0) button.Size = UDim2.new(1, 0, 1, 0) button.BackgroundTransparency = 1 button.BorderSizePixel = 0 button.Image = "rbxassetid://429500449" button.ZIndex = 16302 button.Parent = frame return frame end -- Create a image button template. local function createTouchButtonImage() local button = Instance.new("ImageButton") button.Name = "ButtonImage" button.Active = false button.AnchorPoint = Vector2.new(0.5, 0.5) button.Position = UDim2.new(0.5, 0, 0.5, 0) button.Size = UDim2.new(1, -5, 1, -5) button.BackgroundTransparency = 1 button.BorderSizePixel = 0 button.ImageColor3 = BrickColor.new("Institutional white").Color button.Image = "" button.ZIndex = 16302 return button end -- Create a text button template. local function createTouchButtonText() local button = Instance.new("TextButton") button.Name = "ButtonText" button.Active = false button.AnchorPoint = Vector2.new(0.5, 0.5) button.Position = UDim2.new(0.5, 0, 0.5, 0) button.Size = UDim2.new(1, -2, 1, -2) button.BackgroundTransparency = 1 button.BorderSizePixel = 0 button.TextScaled = true button.TextColor = BrickColor.new("Institutional white") button.Text = "" button.ZIndex = 16303 return button end -- Generate Button Templates buttonFrame = createTouchButtonFrame() buttonImage = createTouchButtonImage() buttonText = createTouchButtonText() createGui() connectDeathEvents() -- Checks to see if the character has already loaded. -- If so, then checks to see if the GUI is active. -- If not, then makes it active. --if localPlayer.Character ~= nil then -- if playerGui:FindFirstChild(CASGuiName) == nil then -- CASGui = CASCloneGui:Clone() -- CASGui.Parent = playerGui -- end --end moduleReady = true print("**** Context Action Service Module Ready") -- ******** Module local module = { enums = { -- Enumeration: CAS Image Frame Codes CASImageCode = { Both = 0; -- Remove both backround image and ring. Background = 1; -- Remove background only. Ring = 2; -- Remove ring only. }; } } module.bindAction = bindAction module.bindActionMulti = bindActionMultiple module.unbindAction = unbindAction module.unbindActionAll = unbindActionAll module.unbindFunction = removeFunctionHandle module.getAllBindings = getAllBindings module.hideTouchButtons = hideTouchButtons module.setGridPosition = setGridPosition module.setIconSize = setIconSize return module