-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathKeybindHandler.lua
More file actions
222 lines (221 loc) · 11.1 KB
/
KeybindHandler.lua
File metadata and controls
222 lines (221 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
local module = {}
local plr = game.Players.LocalPlayer
local char = plr.Character
local pf = plr:WaitForChild("ProfileData") --folder within player, that stores the keybinds as stringvalues
local Settings = require(script.Settings) --Module for storing current keystacks, maxTime for a skill being held, etc
local UIS = game:GetService("UserInputService")
local GUIService = game:GetService("GuiService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local device = "Unknown" --variable for device determination, as different devices have varying paths leading to keybind definitions
local reverseKeybinds = {} --array with remapped keybinds, instead of [SkillName] = Keybind, it's remapped to Keybind = [SkillName] for easier keybind detection
local runningSkills = {} --array to store currently running skills
function module:GetDevice() --function for retriving current device and setting the device variable
if GUIService:IsTenFootInterface() then --only consoles will return true
device = "Console"
elseif UIS.TouchEnabled and not UIS.MouseEnabled then --mobile players dont have mouses, so it would return false
device = "Mobile"
else -- due to previous conditions, it is deduced that the device is PC
device = "Computer"
end
end
local function getInputName(input) --function for retriving inputName from input object, as code is used twice its a function (therefore, applying DRY principle)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
return "MouseLeftButton"
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
return "MouseRightButton"
else
return input.KeyCode.Name
end
--only mouses return a different name due to how i named them, otherwise just return input.KeyCode.Name if its any other key
end
local function getComboString() --due to the system requiring combo's, we retrive current keys in the keystack listed in settings module ,whereby keystack is a dictionary with a maximum of two arrays maximum as we limit combo's to only two keys
if #Settings.KeyStack ~= 2 then return nil end --since combo's require two keys, if it doesnt have 2 keys or is more than that, there is simply no combo at all so we return nil
return Settings.KeyStack[1].key .. "-" .. Settings.KeyStack[2].key
end
local function isValidTiming() --combo's must be done within a certain time between each key, or its invalidated. therefore the use of this function is to check for the validation of timing
if #Settings.KeyStack ~= 2 then return false end --check if theres a possibility for combo's
return (Settings.KeyStack[2].time - Settings.KeyStack[1].time) <= Settings.MaxTime
-- return the true or false depending on the difference of time between the first and second key and compare with the MaxTime set in settings module
end
local function buildReverseKeybindMap(keybinds)
local map = {} --this function builds the reverseKeybinds array, or at least it returns what is supposed to be the reversed keybind array
for _, value in ipairs(keybinds:GetDescendants()) do
if value:IsA("Folder") then continue end --remember, all keybinds are stringvalues
map[value.Value] = value.Name --map Keybind --> SkillName
end
return map --return value
end
function checkcurrentchar()
return pf.CurrentCharacter.Value --check current equipping character from ProfileData folder
end
local function activateSkill(skillName, held)
if skillName == "Emote" then return end --emotes are handled separately, therefore they do not require to be inside this module
local cCharacter = checkcurrentchar() --check current character
local original = skillName
if string.find(skillName, "Character") then skillName = "Switch" end
local skillModule = nil
local mechFolder = ReplicatedStorage.Modules:FindFirstChild("Mechanics") --search whether the skill is a core mechanic first, before searching for the skill in its respective Character folder
if mechFolder then
--if mechanic then set skillModule
skillModule = mechFolder:FindFirstChild(skillName)
end
if not skillModule then
--if no skillModule is set, it means it wasnt a mechanic so now we find the skillModule inside the Character folder
local charFolder = ReplicatedStorage:FindFirstChild("Characters")
if charFolder and charFolder:FindFirstChild(cCharacter) then
local skillsFolder = charFolder[cCharacter]:FindFirstChild("Skills")
skillModule = skillsFolder and skillsFolder:FindFirstChild(skillName)
end
end
if skillModule and skillModule:IsA("ModuleScript") then
--only proceed if the skillModule is found as either inside a Character folder or a mechanics folder
local success, result = pcall(function()
return require(skillModule) --makes sure that skillModule can be required
end)
if success and result.Exec then
--if success and .Exec is a function within the required module then proceed
if skillModule.Name == "Switch" then
-- switch has special variables that need to be passed on, therefore it needs an if statement
result:Exec(plr, plr.Character, string.gsub(original, "Character", ""))
elseif skillModule.Name ~= "Block" then
--same with block
result:Exec(plr, plr.Character, held)
else
-- everything else is consistent with the format of plr, CharacterModel, Activate or Deactivate, Held or Not Held
result:Exec(plr, plr.Character, true, held)
end
end
end
end
local function runSkillRepeatedly(skillName) --if a keybind is being held constantly, we want the skill to be repeatedly runned
if Settings.HeldSkills[skillName] then return end --if its already being held then there is no need to run it repeatedly again
local co = coroutine.create(function()
local timing = 0
local held = false
while Settings.HeldSkills[skillName] do
if timing >= .2 then held = true end
--if its held for >= .2 seconds, then we only check the held variablel as true
activateSkill(skillName, held)
timing += .1
task.wait(0.1)
end
end)
-- set coroutine as an element in HeldSkills array, to kill it after its no longer needed
Settings.HeldSkills[skillName] = co
runningSkills[skillName] = co
coroutine.resume(co)
end
local function stopSkillRepeat(skillName)
if Settings.HeldSkills[skillName] then
Settings.HeldSkills[skillName] = nil
runningSkills[skillName] = nil --kills the coroutine
local result = require(game.ReplicatedStorage.Modules.Mechanics.Block)
result:Exec(plr, plr.Character, false) --turns off block in case its turned on
end
end
function runInput(input, keybinds)
local inputName = getInputName(input) --get inputname from reversed keybinds
local isAllowed = false
for _, inputValue in ipairs(keybinds:GetDescendants()) do
if inputValue:IsA("Folder") then continue end
--combo's in string values are formatted as for example : E-Q, meaning E must be pressed first before Q for it to activate a combo
local comboParts = string.split(inputValue.Value, "-") --hence why it splits when there is -
for _, part in ipairs(comboParts) do
if part == inputName then
isAllowed = true --we allow the combo to run if it exists, if it there is no combo at all
break
end
end
if isAllowed then break end
end
if not isAllowed then return end
for _, entry in ipairs(Settings.KeyStack) do
if entry.key == inputName then return end --if the key is already in the haystack, there is no need to re-add it sincce we dont have double key combo's and we dont want double key combo's
end
table.insert(Settings.KeyStack, {
key = inputName,
time = tick()
}) --insert info into keystack
if #Settings.KeyStack > 2 then --we only allow double key combo's so more than that means we have to remove the first element, which is the old key
table.remove(Settings.KeyStack, 1)
end
if #Settings.KeyStack == 2 then
local combo = getComboString()
if reverseKeybinds[combo] and isValidTiming() then
for _, entry in ipairs(Settings.KeyStack) do
local singleSkill = reverseKeybinds[entry.key]
if singleSkill and Settings.HeldSkills[singleSkill] then
Settings.HeldSkills[singleSkill] = nil
runningSkills[singleSkill] = nil
end
end
activateSkill(reverseKeybinds[combo])
Settings.KeyStack = {}
end
elseif #Settings.KeyStack == 1 then
local singleKey = Settings.KeyStack[1].key
if reverseKeybinds[singleKey] then
runSkillRepeatedly(reverseKeybinds[singleKey])
end
end
task.delay(Settings.MaxTime, function()
if #Settings.KeyStack > 0 and tick() - Settings.KeyStack[1].time >= Settings.MaxTime then
table.remove(Settings.KeyStack, 1)
end
end)
end
function removeInput(input)
local inputName = getInputName(input)
-- basically just removing the input from all arrays that store it when this func is called
for i = #Settings.KeyStack, 1, -1 do
if Settings.KeyStack[i].key == inputName then
table.remove(Settings.KeyStack, i)
break
end
end
for i = #Settings.HeldSkills, 1, -1 do
if Settings.HeldSkills[i].key == inputName then
table.remove(Settings.HeldSkills, i)
break
end
end
if reverseKeybinds[inputName] then
if reverseKeybinds[inputName] == "Block" then
stopSkillRepeat(reverseKeybinds[inputName])
end
if Settings.HeldSkills[reverseKeybinds[inputName]] then
Settings.HeldSkills[reverseKeybinds[inputName]] = nil
runningSkills[reverseKeybinds[inputName]] = nil
end
end
end
function module:Init()
module:GetDevice()
if device == "Mobile" then
plr.PlayerGui.MobileSupport.Enabled = true --enable mobile controls if its mobile
end
if not pf.Keybinds:FindFirstChild(device) then return end --if the device keybinds is found, then only do we continue
local keybinds = pf["Keybinds"]
reverseKeybinds = buildReverseKeybindMap(keybinds) --build reverseKeybinds
UIS.InputBegan:Connect(function(input, gpe)
if not gpe then
runInput(input, keybinds) --run input check upon inputbegan
end
end)
UIS.InputEnded:Connect(function(input, gpe)
if not gpe then
removeInput(input)
end
end)
local count = char:WaitForChild("M1Count") --keeps track of m1 counts, which is a numberValue
local lastChanged = tick()
count.Changed:Connect(function()
lastChanged = tick() --save current time
end)
while task.wait() do
if tick() - lastChanged > 2 then --if m1 hasnt been performed for more than 2 seconds, we reset the count
count.Value = 1
end
end
end
return module