roblox-opencode 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/README.md +112 -122
  2. package/commands/setup-game.md +108 -108
  3. package/commands/sync-check.md +53 -53
  4. package/core/roblox-core.md +93 -93
  5. package/dist/server.js +189 -167
  6. package/package.json +35 -35
  7. package/skills/roblox-analytics/SKILL.md +277 -277
  8. package/skills/roblox-analytics/references/event-batcher.luau +75 -75
  9. package/skills/roblox-animation-vfx/SKILL.md +1325 -1325
  10. package/skills/roblox-architecture/SKILL.md +877 -863
  11. package/skills/roblox-architecture/references/combat-systems.md +1381 -1381
  12. package/skills/roblox-code-review/SKILL.md +686 -686
  13. package/skills/roblox-data/SKILL.md +889 -889
  14. package/skills/roblox-data/references/inventory-systems.md +1729 -1729
  15. package/skills/roblox-debug/SKILL.md +98 -98
  16. package/skills/roblox-gui/SKILL.md +1103 -1103
  17. package/skills/roblox-gui-fusion/SKILL.md +150 -150
  18. package/skills/roblox-gui-fusion/references/inventory.luau +427 -427
  19. package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -579
  20. package/skills/roblox-gui-fusion/references/shop.luau +411 -411
  21. package/skills/roblox-luau-mastery/SKILL.md +1618 -1519
  22. package/skills/roblox-monetization/SKILL.md +1084 -1084
  23. package/skills/roblox-monetization/references/process-receipt.luau +131 -131
  24. package/skills/roblox-networking/SKILL.md +669 -669
  25. package/skills/roblox-networking/references/remote-validator.luau +193 -193
  26. package/skills/roblox-publish-checklist/SKILL.md +127 -127
  27. package/skills/roblox-runtime/SKILL.md +753 -753
  28. package/skills/roblox-sharp-edges/SKILL.md +294 -294
  29. package/skills/roblox-sync/SKILL.md +126 -126
  30. package/skills/roblox-testing/SKILL.md +943 -943
  31. package/skills/roblox-tooling/SKILL.md +149 -149
  32. package/vendor/LICENSES/ProfileStore-LICENSE +201 -201
  33. package/vendor/LICENSES/RbxUtil-LICENSE +7 -7
  34. package/vendor/LICENSES/promise-LICENSE +20 -20
  35. package/vendor/LICENSES/t-LICENSE +21 -21
  36. package/vendor/LICENSES/testez-LICENSE +200 -200
  37. package/vendor/README.md +83 -83
  38. package/vendor/fusion/Animation/ExternalTime.luau +83 -83
  39. package/vendor/fusion/Animation/Spring.luau +321 -321
  40. package/vendor/fusion/Animation/Stopwatch.luau +127 -127
  41. package/vendor/fusion/Animation/Tween.luau +187 -187
  42. package/vendor/fusion/Animation/getTweenDuration.luau +27 -27
  43. package/vendor/fusion/Animation/getTweenRatio.luau +47 -47
  44. package/vendor/fusion/Animation/lerpType.luau +163 -163
  45. package/vendor/fusion/Animation/packType.luau +99 -99
  46. package/vendor/fusion/Animation/springCoefficients.luau +80 -80
  47. package/vendor/fusion/Animation/unpackType.luau +102 -102
  48. package/vendor/fusion/Colour/Oklab.luau +70 -70
  49. package/vendor/fusion/Colour/sRGB.luau +54 -54
  50. package/vendor/fusion/External.luau +167 -167
  51. package/vendor/fusion/ExternalDebug.luau +69 -69
  52. package/vendor/fusion/Graph/Observer.luau +113 -113
  53. package/vendor/fusion/Graph/castToGraph.luau +28 -28
  54. package/vendor/fusion/Graph/change.luau +80 -80
  55. package/vendor/fusion/Graph/depend.luau +32 -32
  56. package/vendor/fusion/Graph/evaluate.luau +55 -55
  57. package/vendor/fusion/Instances/Attribute.luau +57 -57
  58. package/vendor/fusion/Instances/AttributeChange.luau +46 -46
  59. package/vendor/fusion/Instances/AttributeOut.luau +63 -63
  60. package/vendor/fusion/Instances/Child.luau +21 -21
  61. package/vendor/fusion/Instances/Children.luau +147 -147
  62. package/vendor/fusion/Instances/Hydrate.luau +32 -32
  63. package/vendor/fusion/Instances/New.luau +52 -52
  64. package/vendor/fusion/Instances/OnChange.luau +49 -49
  65. package/vendor/fusion/Instances/OnEvent.luau +53 -53
  66. package/vendor/fusion/Instances/Out.luau +69 -69
  67. package/vendor/fusion/Instances/applyInstanceProps.luau +148 -148
  68. package/vendor/fusion/Instances/defaultProps.luau +194 -194
  69. package/vendor/fusion/LICENSE +21 -21
  70. package/vendor/fusion/Logging/formatError.luau +48 -48
  71. package/vendor/fusion/Logging/messages.luau +51 -51
  72. package/vendor/fusion/Logging/parseError.luau +24 -24
  73. package/vendor/fusion/Memory/checkLifetime.luau +133 -133
  74. package/vendor/fusion/Memory/deriveScope.luau +23 -23
  75. package/vendor/fusion/Memory/deriveScopeImpl.luau +44 -44
  76. package/vendor/fusion/Memory/doCleanup.luau +78 -78
  77. package/vendor/fusion/Memory/innerScope.luau +33 -33
  78. package/vendor/fusion/Memory/legacyCleanup.luau +17 -17
  79. package/vendor/fusion/Memory/needsDestruction.luau +16 -16
  80. package/vendor/fusion/Memory/poisonScope.luau +33 -33
  81. package/vendor/fusion/Memory/scopePool.luau +54 -54
  82. package/vendor/fusion/Memory/scoped.luau +26 -26
  83. package/vendor/fusion/Memory/whichLivesLonger.luau +74 -74
  84. package/vendor/fusion/RobloxExternal.luau +97 -97
  85. package/vendor/fusion/State/Computed.luau +138 -138
  86. package/vendor/fusion/State/For/Disassembly.luau +210 -210
  87. package/vendor/fusion/State/For/ForTypes.luau +30 -30
  88. package/vendor/fusion/State/For/init.luau +109 -109
  89. package/vendor/fusion/State/ForKeys.luau +93 -93
  90. package/vendor/fusion/State/ForPairs.luau +96 -96
  91. package/vendor/fusion/State/ForValues.luau +93 -93
  92. package/vendor/fusion/State/Value.luau +87 -87
  93. package/vendor/fusion/State/castToState.luau +25 -25
  94. package/vendor/fusion/State/peek.luau +30 -30
  95. package/vendor/fusion/Types.luau +314 -314
  96. package/vendor/fusion/Utility/Contextual.luau +90 -90
  97. package/vendor/fusion/Utility/Safe.luau +22 -22
  98. package/vendor/fusion/Utility/isSimilar.luau +29 -29
  99. package/vendor/fusion/Utility/merge.luau +35 -35
  100. package/vendor/fusion/Utility/nameOf.luau +34 -34
  101. package/vendor/fusion/Utility/never.luau +13 -13
  102. package/vendor/fusion/Utility/nicknames.luau +10 -10
  103. package/vendor/fusion/Utility/xtypeof.luau +26 -26
  104. package/vendor/fusion/init.luau +82 -82
  105. package/vendor/profilestore/init.luau +2242 -2242
  106. package/vendor/promise/init.luau +1982 -1982
  107. package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -25
  108. package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -228
  109. package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -269
  110. package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -223
  111. package/vendor/rbxutil/buffer-util/Types.luau +60 -60
  112. package/vendor/rbxutil/buffer-util/index.d.ts +153 -153
  113. package/vendor/rbxutil/buffer-util/init.luau +41 -41
  114. package/vendor/rbxutil/buffer-util/package.json +16 -16
  115. package/vendor/rbxutil/buffer-util/wally.toml +9 -9
  116. package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -232
  117. package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -156
  118. package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -109
  119. package/vendor/rbxutil/comm/Client/init.luau +135 -135
  120. package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -295
  121. package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -211
  122. package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -211
  123. package/vendor/rbxutil/comm/Server/init.luau +140 -140
  124. package/vendor/rbxutil/comm/Types.luau +18 -18
  125. package/vendor/rbxutil/comm/Util.luau +27 -27
  126. package/vendor/rbxutil/comm/init.luau +35 -35
  127. package/vendor/rbxutil/comm/wally.toml +13 -13
  128. package/vendor/rbxutil/component/init.luau +759 -759
  129. package/vendor/rbxutil/component/init.test.luau +311 -311
  130. package/vendor/rbxutil/component/wally.toml +14 -14
  131. package/vendor/rbxutil/concur/init.luau +542 -542
  132. package/vendor/rbxutil/concur/init.test.luau +364 -364
  133. package/vendor/rbxutil/concur/wally.toml +8 -8
  134. package/vendor/rbxutil/enum-list/init.luau +101 -101
  135. package/vendor/rbxutil/enum-list/init.test.luau +91 -91
  136. package/vendor/rbxutil/enum-list/wally.toml +8 -8
  137. package/vendor/rbxutil/find/index.d.ts +20 -20
  138. package/vendor/rbxutil/find/init.luau +44 -44
  139. package/vendor/rbxutil/find/package.json +17 -17
  140. package/vendor/rbxutil/find/wally.toml +8 -8
  141. package/vendor/rbxutil/input/Gamepad.luau +559 -559
  142. package/vendor/rbxutil/input/Keyboard.luau +124 -124
  143. package/vendor/rbxutil/input/Mouse.luau +278 -278
  144. package/vendor/rbxutil/input/PreferredInput.luau +91 -91
  145. package/vendor/rbxutil/input/Touch.luau +120 -120
  146. package/vendor/rbxutil/input/init.luau +33 -33
  147. package/vendor/rbxutil/input/wally.toml +12 -12
  148. package/vendor/rbxutil/loader/index.d.ts +15 -15
  149. package/vendor/rbxutil/loader/init.luau +137 -137
  150. package/vendor/rbxutil/loader/wally.toml +8 -8
  151. package/vendor/rbxutil/log/index.d.ts +38 -38
  152. package/vendor/rbxutil/log/init.luau +746 -746
  153. package/vendor/rbxutil/log/wally.toml +8 -8
  154. package/vendor/rbxutil/net/init.luau +190 -190
  155. package/vendor/rbxutil/net/wally.toml +8 -8
  156. package/vendor/rbxutil/option/index.d.ts +44 -44
  157. package/vendor/rbxutil/option/init.luau +489 -489
  158. package/vendor/rbxutil/option/init.test.luau +342 -342
  159. package/vendor/rbxutil/option/wally.toml +8 -8
  160. package/vendor/rbxutil/pid/index.d.ts +53 -53
  161. package/vendor/rbxutil/pid/init.luau +195 -195
  162. package/vendor/rbxutil/pid/package.json +16 -16
  163. package/vendor/rbxutil/pid/wally.toml +9 -9
  164. package/vendor/rbxutil/quaternion/index.d.ts +117 -117
  165. package/vendor/rbxutil/quaternion/init.luau +570 -570
  166. package/vendor/rbxutil/quaternion/package.json +16 -16
  167. package/vendor/rbxutil/quaternion/wally.toml +9 -9
  168. package/vendor/rbxutil/query/index.d.ts +43 -43
  169. package/vendor/rbxutil/query/init.luau +117 -117
  170. package/vendor/rbxutil/query/package.json +18 -18
  171. package/vendor/rbxutil/query/wally.toml +9 -9
  172. package/vendor/rbxutil/sequent/index.d.ts +28 -28
  173. package/vendor/rbxutil/sequent/init.luau +340 -340
  174. package/vendor/rbxutil/sequent/package.json +16 -16
  175. package/vendor/rbxutil/sequent/wally.toml +9 -9
  176. package/vendor/rbxutil/ser/init.luau +175 -175
  177. package/vendor/rbxutil/ser/init.test.luau +50 -50
  178. package/vendor/rbxutil/ser/wally.toml +11 -11
  179. package/vendor/rbxutil/shake/index.d.ts +36 -36
  180. package/vendor/rbxutil/shake/init.luau +532 -532
  181. package/vendor/rbxutil/shake/init.test.luau +267 -267
  182. package/vendor/rbxutil/shake/package.json +16 -16
  183. package/vendor/rbxutil/shake/wally.toml +9 -9
  184. package/vendor/rbxutil/signal/index.d.ts +100 -100
  185. package/vendor/rbxutil/signal/init.luau +432 -432
  186. package/vendor/rbxutil/signal/init.test.luau +190 -190
  187. package/vendor/rbxutil/signal/package.json +17 -17
  188. package/vendor/rbxutil/signal/wally.toml +9 -9
  189. package/vendor/rbxutil/silo/TableWatcher.luau +65 -65
  190. package/vendor/rbxutil/silo/Util.luau +55 -55
  191. package/vendor/rbxutil/silo/init.luau +338 -338
  192. package/vendor/rbxutil/silo/init.test.luau +215 -215
  193. package/vendor/rbxutil/silo/wally.toml +8 -8
  194. package/vendor/rbxutil/spring/index.d.ts +40 -40
  195. package/vendor/rbxutil/spring/init.luau +97 -97
  196. package/vendor/rbxutil/spring/package.json +17 -17
  197. package/vendor/rbxutil/spring/wally.toml +8 -8
  198. package/vendor/rbxutil/stream/index.d.ts +88 -88
  199. package/vendor/rbxutil/stream/init.luau +597 -597
  200. package/vendor/rbxutil/stream/package.json +18 -18
  201. package/vendor/rbxutil/stream/wally.toml +9 -9
  202. package/vendor/rbxutil/streamable/Streamable.luau +202 -202
  203. package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -80
  204. package/vendor/rbxutil/streamable/init.luau +8 -8
  205. package/vendor/rbxutil/streamable/wally.toml +12 -12
  206. package/vendor/rbxutil/symbol/init.luau +56 -56
  207. package/vendor/rbxutil/symbol/init.test.luau +37 -37
  208. package/vendor/rbxutil/symbol/wally.toml +8 -8
  209. package/vendor/rbxutil/table-util/init.luau +938 -938
  210. package/vendor/rbxutil/table-util/init.test.luau +439 -439
  211. package/vendor/rbxutil/task-queue/index.d.ts +27 -27
  212. package/vendor/rbxutil/task-queue/init.luau +97 -97
  213. package/vendor/rbxutil/task-queue/wally.toml +8 -8
  214. package/vendor/rbxutil/timer/index.d.ts +81 -81
  215. package/vendor/rbxutil/timer/init.luau +249 -249
  216. package/vendor/rbxutil/timer/init.test.luau +73 -73
  217. package/vendor/rbxutil/timer/wally.toml +11 -11
  218. package/vendor/rbxutil/tree/index.d.ts +15 -15
  219. package/vendor/rbxutil/tree/init.luau +137 -137
  220. package/vendor/rbxutil/tree/wally.toml +8 -8
  221. package/vendor/rbxutil/trove/index.d.ts +46 -46
  222. package/vendor/rbxutil/trove/init.luau +787 -787
  223. package/vendor/rbxutil/trove/init.test.luau +203 -203
  224. package/vendor/rbxutil/trove/wally.toml +8 -8
  225. package/vendor/rbxutil/typed-remote/init.luau +196 -196
  226. package/vendor/rbxutil/typed-remote/wally.toml +8 -8
  227. package/vendor/rbxutil/wait-for/index.d.ts +17 -17
  228. package/vendor/rbxutil/wait-for/init.luau +257 -257
  229. package/vendor/rbxutil/wait-for/init.test.luau +182 -182
  230. package/vendor/rbxutil/wait-for/wally.toml +11 -11
  231. package/vendor/t/t.lua +1350 -1350
  232. package/vendor/testez/Context.lua +26 -26
  233. package/vendor/testez/Expectation.lua +311 -311
  234. package/vendor/testez/ExpectationContext.lua +38 -38
  235. package/vendor/testez/LifecycleHooks.lua +89 -89
  236. package/vendor/testez/Reporters/TeamCityReporter.lua +101 -101
  237. package/vendor/testez/Reporters/TextReporter.lua +105 -105
  238. package/vendor/testez/Reporters/TextReporterQuiet.lua +96 -96
  239. package/vendor/testez/TestBootstrap.lua +146 -146
  240. package/vendor/testez/TestEnum.lua +27 -27
  241. package/vendor/testez/TestPlan.lua +304 -304
  242. package/vendor/testez/TestPlanner.lua +39 -39
  243. package/vendor/testez/TestResults.lua +111 -111
  244. package/vendor/testez/TestRunner.lua +188 -188
  245. package/vendor/testez/TestSession.lua +243 -243
  246. package/vendor/testez/init.lua +39 -39
@@ -1,559 +1,559 @@
1
- -- Gamepad
2
- -- Stephen Leitnick
3
- -- December 23, 2021
4
-
5
- local Signal = require(script.Parent.Parent.Signal)
6
- local Trove = require(script.Parent.Parent.Trove)
7
-
8
- local GuiService = game:GetService("GuiService")
9
- local HapticService = game:GetService("HapticService")
10
- local RunService = game:GetService("RunService")
11
- local UserInputService = game:GetService("UserInputService")
12
-
13
- local function ApplyDeadzone(value: number, threshold: number): number
14
- if math.abs(value) < threshold then
15
- return 0
16
- end
17
- return ((math.abs(value) - threshold) / (1 - threshold)) * math.sign(value)
18
- end
19
-
20
- local function GetActiveGamepad(): Enum.UserInputType?
21
- local activeGamepad = nil
22
- local navGamepads = UserInputService:GetNavigationGamepads()
23
- if #navGamepads > 1 then
24
- for _, navGamepad in navGamepads do
25
- if activeGamepad == nil or navGamepad.Value < activeGamepad.Value then
26
- activeGamepad = navGamepad
27
- end
28
- end
29
- else
30
- local connectedGamepads = UserInputService:GetConnectedGamepads()
31
- for _, connectedGamepad in connectedGamepads do
32
- if activeGamepad == nil or connectedGamepad.Value < activeGamepad.Value then
33
- activeGamepad = connectedGamepad
34
- end
35
- end
36
- end
37
- if activeGamepad and not UserInputService:GetGamepadConnected(activeGamepad) then
38
- activeGamepad = nil
39
- end
40
- return activeGamepad
41
- end
42
-
43
- local function HeartbeatDelay(duration: number, callback: () -> nil): RBXScriptConnection
44
- local start = time()
45
- local connection
46
- connection = RunService.Heartbeat:Connect(function()
47
- local elapsed = time() - start
48
- if elapsed >= duration then
49
- connection:Disconnect()
50
- callback()
51
- end
52
- end)
53
- return connection
54
- end
55
-
56
- --[=[
57
- @class Gamepad
58
- @client
59
-
60
- The Gamepad class is part of the Input package.
61
-
62
- ```lua
63
- local Gamepad = require(packages.Input).Gamepad
64
-
65
- local gamepad = Gamepad.new()
66
- ```
67
- ]=]
68
- local Gamepad = {}
69
- Gamepad.__index = Gamepad
70
-
71
- --[=[
72
- @within Gamepad
73
- @prop ButtonDown Signal<(button: Enum.KeyCode, processed: boolean)>
74
- @readonly
75
- The ButtonDown signal fires when a gamepad button is pressed
76
- down. The pressed KeyCode is passed to the signal, along with
77
- whether or not the event was processed.
78
-
79
- ```lua
80
- gamepad.ButtonDown:Connect(function(button: Enum.KeyCode, processed: boolean)
81
- print("Button down", button, processed)
82
- end)
83
- ```
84
- ]=]
85
-
86
- --[=[
87
- @within Gamepad
88
- @prop ButtonUp Signal<(button: Enum.KeyCode, processed: boolean)>
89
- @readonly
90
- The ButtonUp signal fires when a gamepad button is released.
91
- The released KeyCode is passed to the signal, along with
92
- whether or not the event was processed.
93
-
94
- ```lua
95
- gamepad.ButtonUp:Connect(function(button: Enum.KeyCode, processed: boolean)
96
- print("Button up", button, processed)
97
- end)
98
- ```
99
- ]=]
100
-
101
- --[=[
102
- @within Gamepad
103
- @prop Connected Signal
104
- @readonly
105
- Fires when the gamepad is connected. This will _not_ fire if the
106
- active gamepad is switched. To detect switching to different
107
- active gamepads, use the `GamepadChanged` signal.
108
-
109
- There is also a `gamepad:IsConnected()` method.
110
-
111
- ```lua
112
- gamepad.Connected:Connect(function()
113
- print("Connected")
114
- end)
115
- ```
116
- ]=]
117
-
118
- --[=[
119
- @within Gamepad
120
- @prop Disconnected Signal
121
- @readonly
122
- Fires when the gamepad is disconnected. This will _not_ fire if the
123
- active gamepad is switched. To detect switching to different
124
- active gamepads, use the `GamepadChanged` signal.
125
-
126
- There is also a `gamepad:IsConnected()` method.
127
-
128
- ```lua
129
- gamepad.Disconnected:Connect(function()
130
- print("Disconnected")
131
- end)
132
- ```
133
- ]=]
134
-
135
- --[=[
136
- @within Gamepad
137
- @prop GamepadChanged Signal<gamepad: Enum.UserInputType>
138
- @readonly
139
- Fires when the active gamepad switches. Internally, the gamepad
140
- object will always wrap around the active gamepad, so nothing
141
- needs to be changed.
142
-
143
- ```lua
144
- gamepad.GamepadChanged:Connect(function(newGamepad: Enum.UserInputType)
145
- print("Active gamepad changed to:", newGamepad)
146
- end)
147
- ```
148
- ]=]
149
-
150
- --[=[
151
- @within Gamepad
152
- @prop DefaultDeadzone number
153
-
154
- :::info Default
155
- Defaults to `0.05`
156
- :::
157
-
158
- The default deadzone used for trigger and thumbstick
159
- analog readings. It is usually best to set this to
160
- a small value, or allow players to set this option
161
- themselves in an in-game settings menu.
162
-
163
- The `GetThumbstick` and `GetTrigger` methods also allow
164
- a deadzone value to be passed in, which overrides this
165
- value.
166
- ]=]
167
-
168
- --[=[
169
- @within Gamepad
170
- @prop SupportsVibration boolean
171
- @readonly
172
- Flag to indicate if the currently-active gamepad supports
173
- haptic motor vibration.
174
-
175
- It is safe to use the motor methods on the gamepad without
176
- checking this value, but nothing will happen if the motors
177
- are not supported.
178
- ]=]
179
-
180
- --[=[
181
- @within Gamepad
182
- @prop State GamepadState
183
- @readonly
184
- Maps KeyCodes to the matching InputObjects within the gamepad.
185
- These can be used to directly read the current input state of
186
- a given part of the gamepad. For most cases, the given methods
187
- and properties of `Gamepad` should make use of this table quite
188
- rare, but it is provided for special use-cases that might occur.
189
-
190
- :::note Do Not Cache
191
- These state objects will change if the active gamepad changes.
192
- Because a player might switch up gamepads during playtime, it cannot
193
- be assumed that these state objects will always be the same. Thus
194
- they should be accessed directly from this `State` table anytime they
195
- need to be used.
196
- :::
197
-
198
- ```lua
199
- local leftThumbstick = gamepad.State[Enum.KeyCode.Thumbstick1]
200
- print(leftThumbstick.Position)
201
- -- It would be better to use gamepad:GetThumbstick(Enum.KeyCode.Thumbstick1),
202
- -- but this is just an example of direct state access.
203
- ```
204
- ]=]
205
-
206
- --[=[
207
- @within Gamepad
208
- @type GamepadState {[Enum.KeyCode]: InputObject}
209
- ]=]
210
-
211
- --[=[
212
- @param gamepad Enum.UserInputType?
213
- @return Gamepad
214
- Constructs a gamepad object.
215
-
216
- If no gamepad UserInputType is provided, this object will always wrap
217
- around the currently-active gamepad, even if it changes. In most cases
218
- where input is needed from just the primary gamepad used by the player,
219
- leaving the `gamepad` argument blank is preferred.
220
-
221
- Only include the `gamepad` argument when it is necessary to hard-lock
222
- the object to a specific gamepad input type.
223
-
224
- ```lua
225
- -- In most cases, construct the gamepad as such:
226
- local gamepad = Gamepad.new()
227
-
228
- -- If the exact UserInputType gamepad is needed, pass it as such:
229
- local gamepad = Gamepad.new(Enum.UserInputType.Gamepad1)
230
- ```
231
- ]=]
232
- function Gamepad.new(gamepad: Enum.UserInputType?)
233
- local self = setmetatable({}, Gamepad)
234
-
235
- self._trove = Trove.new()
236
- self._gamepadTrove = self._trove:Construct(Trove)
237
- self.ButtonDown = self._trove:Construct(Signal)
238
- self.ButtonUp = self._trove:Construct(Signal)
239
- self.Connected = self._trove:Construct(Signal)
240
- self.Disconnected = self._trove:Construct(Signal)
241
- self.GamepadChanged = self._trove:Construct(Signal)
242
- self.DefaultDeadzone = 0.05
243
- self.SupportsVibration = false
244
- self.State = {}
245
-
246
- self:_setupGamepad(gamepad)
247
- self:_setupMotors()
248
-
249
- return self
250
- end
251
-
252
- function Gamepad:_setupActiveGamepad(gamepad: Enum.UserInputType?)
253
- local lastGamepad = self._gamepad
254
- if gamepad == lastGamepad then
255
- return
256
- end
257
-
258
- self._gamepadTrove:Clean()
259
- table.clear(self.State)
260
- self.SupportsVibration = if gamepad then HapticService:IsVibrationSupported(gamepad) else false
261
-
262
- self._gamepad = gamepad
263
-
264
- -- Stop if disconnected:
265
- if not gamepad then
266
- self.Disconnected:Fire()
267
- self.GamepadChanged:Fire(nil)
268
- return
269
- end
270
-
271
- for _, inputObject in UserInputService:GetGamepadState(gamepad) do
272
- self.State[inputObject.KeyCode] = inputObject
273
- end
274
-
275
- self._gamepadTrove:Add(self, "StopMotors")
276
-
277
- self._gamepadTrove:Connect(UserInputService.InputBegan, function(input, processed)
278
- if input.UserInputType == gamepad then
279
- self.ButtonDown:Fire(input.KeyCode, processed)
280
- end
281
- end)
282
-
283
- self._gamepadTrove:Connect(UserInputService.InputEnded, function(input, processed)
284
- if input.UserInputType == gamepad then
285
- self.ButtonUp:Fire(input.KeyCode, processed)
286
- end
287
- end)
288
-
289
- if lastGamepad == nil then
290
- self.Connected:Fire()
291
- end
292
- self.GamepadChanged:Fire(gamepad)
293
- end
294
-
295
- function Gamepad:_setupGamepad(forcedGamepad: Enum.UserInputType?)
296
- if forcedGamepad then
297
- -- Forced gamepad:
298
-
299
- self._trove:Connect(UserInputService.GamepadConnected, function(gp)
300
- if gp == forcedGamepad then
301
- self:_setupActiveGamepad(forcedGamepad)
302
- end
303
- end)
304
-
305
- self._trove:Connect(UserInputService.GamepadDisconnected, function(gp)
306
- if gp == forcedGamepad then
307
- self:_setupActiveGamepad(nil)
308
- end
309
- end)
310
-
311
- if UserInputService:GetGamepadConnected(forcedGamepad) then
312
- self:_setupActiveGamepad(forcedGamepad)
313
- end
314
- else
315
- -- Dynamic gamepad:
316
-
317
- local function CheckToSetupActive()
318
- local active = GetActiveGamepad()
319
- if active ~= self._gamepad then
320
- self:_setupActiveGamepad(active)
321
- end
322
- end
323
-
324
- self._trove:Connect(UserInputService.GamepadConnected, CheckToSetupActive)
325
- self._trove:Connect(UserInputService.GamepadDisconnected, CheckToSetupActive)
326
- self:_setupActiveGamepad(GetActiveGamepad())
327
- end
328
- end
329
-
330
- function Gamepad:_setupMotors()
331
- self._setMotorIds = {}
332
- for _, motor in Enum.VibrationMotor:GetEnumItems() do
333
- self._setMotorIds[motor] = 0
334
- end
335
- end
336
-
337
- --[=[
338
- @param thumbstick Enum.KeyCode
339
- @param deadzoneThreshold number?
340
- @return Vector2
341
- Gets the position of the given thumbstick. The two thumbstick
342
- KeyCodes are `Enum.KeyCode.Thumbstick1` and `Enum.KeyCode.Thumbstick2`.
343
-
344
- If `deadzoneThreshold` is not included, the `DefaultDeadzone` value is
345
- used instead.
346
-
347
- ```lua
348
- local leftThumbstick = gamepad:GetThumbstick(Enum.KeyCode.Thumbstick1)
349
- print("Left thumbstick position", leftThumbstick)
350
- ```
351
- ]=]
352
- function Gamepad:GetThumbstick(thumbstick: Enum.KeyCode, deadzoneThreshold: number?): Vector2
353
- local pos = self.State[thumbstick].Position
354
- local deadzone = deadzoneThreshold or self.DefaultDeadzone
355
- return Vector2.new(ApplyDeadzone(pos.X, deadzone), ApplyDeadzone(pos.Y, deadzone))
356
- end
357
-
358
- --[=[
359
- @param trigger KeyCode
360
- @param deadzoneThreshold number?
361
- @return number
362
- Gets the position of the given trigger. The triggers are usually going
363
- to be `Enum.KeyCode.ButtonL2` and `Enum.KeyCode.ButtonR2`. These trigger
364
- buttons are analog, and will output a value between the range of [0, 1].
365
-
366
- If `deadzoneThreshold` is not included, the `DefaultDeadzone` value is
367
- used instead.
368
-
369
- ```lua
370
- local triggerAmount = gamepad:GetTrigger(Enum.KeyCode.ButtonR2)
371
- print(triggerAmount)
372
- ```
373
- ]=]
374
- function Gamepad:GetTrigger(trigger: Enum.KeyCode, deadzoneThreshold: number?): number
375
- return ApplyDeadzone(self.State[trigger].Position.Z, deadzoneThreshold or self.DefaultDeadzone)
376
- end
377
-
378
- --[=[
379
- @param gamepadButton KeyCode
380
- @return boolean
381
- Returns `true` if the given button is down. This includes
382
- any button on the gamepad, such as `Enum.KeyCode.ButtonA`,
383
- `Enum.KeyCode.ButtonL3`, `Enum.KeyCode.DPadUp`, etc.
384
-
385
- ```lua
386
- -- Check if the 'A' button is down:
387
- if gamepad:IsButtonDown(Enum.KeyCode.ButtonA) then
388
- print("ButtonA is down")
389
- end
390
- ```
391
- ]=]
392
- function Gamepad:IsButtonDown(gamepadButton: Enum.KeyCode): boolean
393
- return UserInputService:IsGamepadButtonDown(self._gamepad, gamepadButton)
394
- end
395
-
396
- --[=[
397
- @param motor Enum.VibrationMotor
398
- @return boolean
399
- Returns `true` if the given motor is supported.
400
-
401
- ```lua
402
- -- Pulse the trigger (e.g. shooting a weapon), but fall back to
403
- -- the large motor if not supported:
404
- local motor = Enum.VibrationMotor.Large
405
- if gamepad:IsMotorSupported(Enum.VibrationMotor.RightTrigger) then
406
- motor = Enum.VibrationMotor.RightTrigger
407
- end
408
- gamepad:PulseMotor(motor, 1, 0.1)
409
- ```
410
- ]=]
411
- function Gamepad:IsMotorSupported(motor: Enum.VibrationMotor): boolean
412
- return HapticService:IsMotorSupported(self._gamepad, motor)
413
- end
414
-
415
- --[=[
416
- @param motor Enum.VibrationMotor
417
- @param intensity number
418
- Sets the gamepad's haptic motor to a certain intensity. The
419
- intensity value is a number in the range of [0, 1].
420
-
421
- ```lua
422
- gamepad:SetMotor(Enum.VibrationMotor.Large, 0.5)
423
- ```
424
- ]=]
425
- function Gamepad:SetMotor(motor: Enum.VibrationMotor, intensity: number): number
426
- self._setMotorIds[motor] += 1
427
- local id = self._setMotorIds[motor]
428
- HapticService:SetMotor(self._gamepad, motor, intensity)
429
-
430
- return id
431
- end
432
-
433
- --[=[
434
- @param motor Enum.VibrationMotor
435
- @param intensity number
436
- @param duration number
437
- Sets the gamepad's haptic motor to a certain intensity for a given
438
- period of time. The motor will stop vibrating after the given
439
- `duration` has elapsed.
440
-
441
- Calling any motor setter methods (e.g. `SetMotor`, `PulseMotor`,
442
- `StopMotor`) _after_ calling this method will override the pulse.
443
- For instance, if `PulseMotor` is called, and then `SetMotor` is
444
- called right afterwards, `SetMotor` will take precedent.
445
-
446
- ```lua
447
- -- Pulse the large motor for 0.2 seconds with an intensity of 90%:
448
- gamepad:PulseMotor(Enum.VibrationMotor.Large, 0.9, 0.2)
449
-
450
- -- Example of PulseMotor being overridden:
451
- gamepad:PulseMotor(Enum.VibrationMotor.Large, 1, 3)
452
- task.wait(0.1)
453
- gamepad:SetMotor(Enum.VibrationMotor.Large, 0.5)
454
- -- Now the pulse won't shut off the motor after 3 seconds,
455
- -- because SetMotor was called, which cancels the pulse.
456
- ```
457
- ]=]
458
- function Gamepad:PulseMotor(motor: Enum.VibrationMotor, intensity: number, duration: number)
459
- local id = self:SetMotor(motor, intensity)
460
-
461
- local heartbeat = HeartbeatDelay(duration, function()
462
- if self._setMotorIds[motor] ~= id then
463
- return
464
- end
465
- self:StopMotor(motor)
466
- end)
467
-
468
- self._gamepadTrove:Add(heartbeat)
469
- end
470
-
471
- --[=[
472
- @param motor Enum.VibrationMotor
473
- Stops the given motor. This is equivalent to calling
474
- `gamepad:SetMotor(motor, 0)`.
475
-
476
- ```lua
477
- gamepad:SetMotor(Enum.VibrationMotor.Large, 1)
478
- task.wait(0.1)
479
- gamepad:StopMotor(Enum.VibrationMotor.Large)
480
- ```
481
- ]=]
482
- function Gamepad:StopMotor(motor: Enum.VibrationMotor)
483
- self:SetMotor(motor, 0)
484
- end
485
-
486
- --[=[
487
- Stops all motors on the gamepad.
488
-
489
- ```lua
490
- gamepad:SetMotor(Enum.VibrationMotor.Large, 1)
491
- gamepad:SetMotor(Enum.VibrationMotor.Small, 1)
492
- task.wait(0.1)
493
- gamepad:StopMotors()
494
- ```
495
- ]=]
496
- function Gamepad:StopMotors()
497
- for _, motor in Enum.VibrationMotor:GetEnumItems() do
498
- if self:IsMotorSupported(motor) then
499
- self:StopMotor(motor)
500
- end
501
- end
502
- end
503
-
504
- --[=[
505
- @return boolean
506
- Returns `true` if the gamepad is currently connected.
507
- ]=]
508
- function Gamepad:IsConnected(): boolean
509
- return if self._gamepad then UserInputService:GetGamepadConnected(self._gamepad) else false
510
- end
511
-
512
- --[=[
513
- @return Enum.UserInputType?
514
- Gets the current gamepad UserInputType that the gamepad object
515
- is using. This will be `nil` if there is no connected gamepad.
516
- ]=]
517
- function Gamepad:GetUserInputType(): Enum.UserInputType?
518
- return self._gamepad
519
- end
520
-
521
- --[=[
522
- @param enabled boolean
523
- Sets the [`GuiService.AutoSelectGuiEnabled`](https://developer.roblox.com/en-us/api-reference/property/GuiService/AutoSelectGuiEnabled)
524
- property.
525
-
526
- This sets whether or not the Select button on a gamepad will try to auto-select
527
- a GUI object on screen. This does _not_ turn on/off GUI gamepad navigation,
528
- but just the initial selection using the Select button.
529
-
530
- For UX purposes, it usually is preferred to set this to `false` and then
531
- manually set the [`GuiService.SelectedObject`](https://developer.roblox.com/en-us/api-reference/property/GuiService/SelectedObject)
532
- property within code to set the selected object for gamepads.
533
-
534
- ```lua
535
- gamepad:SetAutoSelectGui(false)
536
- game:GetService("GuiService").SelectedObject = someGuiObject
537
- ```
538
- ]=]
539
- function Gamepad:SetAutoSelectGui(enabled: boolean)
540
- GuiService.AutoSelectGuiEnabled = enabled
541
- end
542
-
543
- --[=[
544
- @return boolean
545
- Returns the [`GuiService.AutoSelectGuiEnabled`](https://developer.roblox.com/en-us/api-reference/property/GuiService/AutoSelectGuiEnabled)
546
- property.
547
- ]=]
548
- function Gamepad:IsAutoSelectGuiEnabled(): boolean
549
- return GuiService.AutoSelectGuiEnabled
550
- end
551
-
552
- --[=[
553
- Destroys the gamepad object.
554
- ]=]
555
- function Gamepad:Destroy()
556
- self._trove:Destroy()
557
- end
558
-
559
- return Gamepad
1
+ -- Gamepad
2
+ -- Stephen Leitnick
3
+ -- December 23, 2021
4
+
5
+ local Signal = require(script.Parent.Parent.Signal)
6
+ local Trove = require(script.Parent.Parent.Trove)
7
+
8
+ local GuiService = game:GetService("GuiService")
9
+ local HapticService = game:GetService("HapticService")
10
+ local RunService = game:GetService("RunService")
11
+ local UserInputService = game:GetService("UserInputService")
12
+
13
+ local function ApplyDeadzone(value: number, threshold: number): number
14
+ if math.abs(value) < threshold then
15
+ return 0
16
+ end
17
+ return ((math.abs(value) - threshold) / (1 - threshold)) * math.sign(value)
18
+ end
19
+
20
+ local function GetActiveGamepad(): Enum.UserInputType?
21
+ local activeGamepad = nil
22
+ local navGamepads = UserInputService:GetNavigationGamepads()
23
+ if #navGamepads > 1 then
24
+ for _, navGamepad in navGamepads do
25
+ if activeGamepad == nil or navGamepad.Value < activeGamepad.Value then
26
+ activeGamepad = navGamepad
27
+ end
28
+ end
29
+ else
30
+ local connectedGamepads = UserInputService:GetConnectedGamepads()
31
+ for _, connectedGamepad in connectedGamepads do
32
+ if activeGamepad == nil or connectedGamepad.Value < activeGamepad.Value then
33
+ activeGamepad = connectedGamepad
34
+ end
35
+ end
36
+ end
37
+ if activeGamepad and not UserInputService:GetGamepadConnected(activeGamepad) then
38
+ activeGamepad = nil
39
+ end
40
+ return activeGamepad
41
+ end
42
+
43
+ local function HeartbeatDelay(duration: number, callback: () -> nil): RBXScriptConnection
44
+ local start = time()
45
+ local connection
46
+ connection = RunService.Heartbeat:Connect(function()
47
+ local elapsed = time() - start
48
+ if elapsed >= duration then
49
+ connection:Disconnect()
50
+ callback()
51
+ end
52
+ end)
53
+ return connection
54
+ end
55
+
56
+ --[=[
57
+ @class Gamepad
58
+ @client
59
+
60
+ The Gamepad class is part of the Input package.
61
+
62
+ ```lua
63
+ local Gamepad = require(packages.Input).Gamepad
64
+
65
+ local gamepad = Gamepad.new()
66
+ ```
67
+ ]=]
68
+ local Gamepad = {}
69
+ Gamepad.__index = Gamepad
70
+
71
+ --[=[
72
+ @within Gamepad
73
+ @prop ButtonDown Signal<(button: Enum.KeyCode, processed: boolean)>
74
+ @readonly
75
+ The ButtonDown signal fires when a gamepad button is pressed
76
+ down. The pressed KeyCode is passed to the signal, along with
77
+ whether or not the event was processed.
78
+
79
+ ```lua
80
+ gamepad.ButtonDown:Connect(function(button: Enum.KeyCode, processed: boolean)
81
+ print("Button down", button, processed)
82
+ end)
83
+ ```
84
+ ]=]
85
+
86
+ --[=[
87
+ @within Gamepad
88
+ @prop ButtonUp Signal<(button: Enum.KeyCode, processed: boolean)>
89
+ @readonly
90
+ The ButtonUp signal fires when a gamepad button is released.
91
+ The released KeyCode is passed to the signal, along with
92
+ whether or not the event was processed.
93
+
94
+ ```lua
95
+ gamepad.ButtonUp:Connect(function(button: Enum.KeyCode, processed: boolean)
96
+ print("Button up", button, processed)
97
+ end)
98
+ ```
99
+ ]=]
100
+
101
+ --[=[
102
+ @within Gamepad
103
+ @prop Connected Signal
104
+ @readonly
105
+ Fires when the gamepad is connected. This will _not_ fire if the
106
+ active gamepad is switched. To detect switching to different
107
+ active gamepads, use the `GamepadChanged` signal.
108
+
109
+ There is also a `gamepad:IsConnected()` method.
110
+
111
+ ```lua
112
+ gamepad.Connected:Connect(function()
113
+ print("Connected")
114
+ end)
115
+ ```
116
+ ]=]
117
+
118
+ --[=[
119
+ @within Gamepad
120
+ @prop Disconnected Signal
121
+ @readonly
122
+ Fires when the gamepad is disconnected. This will _not_ fire if the
123
+ active gamepad is switched. To detect switching to different
124
+ active gamepads, use the `GamepadChanged` signal.
125
+
126
+ There is also a `gamepad:IsConnected()` method.
127
+
128
+ ```lua
129
+ gamepad.Disconnected:Connect(function()
130
+ print("Disconnected")
131
+ end)
132
+ ```
133
+ ]=]
134
+
135
+ --[=[
136
+ @within Gamepad
137
+ @prop GamepadChanged Signal<gamepad: Enum.UserInputType>
138
+ @readonly
139
+ Fires when the active gamepad switches. Internally, the gamepad
140
+ object will always wrap around the active gamepad, so nothing
141
+ needs to be changed.
142
+
143
+ ```lua
144
+ gamepad.GamepadChanged:Connect(function(newGamepad: Enum.UserInputType)
145
+ print("Active gamepad changed to:", newGamepad)
146
+ end)
147
+ ```
148
+ ]=]
149
+
150
+ --[=[
151
+ @within Gamepad
152
+ @prop DefaultDeadzone number
153
+
154
+ :::info Default
155
+ Defaults to `0.05`
156
+ :::
157
+
158
+ The default deadzone used for trigger and thumbstick
159
+ analog readings. It is usually best to set this to
160
+ a small value, or allow players to set this option
161
+ themselves in an in-game settings menu.
162
+
163
+ The `GetThumbstick` and `GetTrigger` methods also allow
164
+ a deadzone value to be passed in, which overrides this
165
+ value.
166
+ ]=]
167
+
168
+ --[=[
169
+ @within Gamepad
170
+ @prop SupportsVibration boolean
171
+ @readonly
172
+ Flag to indicate if the currently-active gamepad supports
173
+ haptic motor vibration.
174
+
175
+ It is safe to use the motor methods on the gamepad without
176
+ checking this value, but nothing will happen if the motors
177
+ are not supported.
178
+ ]=]
179
+
180
+ --[=[
181
+ @within Gamepad
182
+ @prop State GamepadState
183
+ @readonly
184
+ Maps KeyCodes to the matching InputObjects within the gamepad.
185
+ These can be used to directly read the current input state of
186
+ a given part of the gamepad. For most cases, the given methods
187
+ and properties of `Gamepad` should make use of this table quite
188
+ rare, but it is provided for special use-cases that might occur.
189
+
190
+ :::note Do Not Cache
191
+ These state objects will change if the active gamepad changes.
192
+ Because a player might switch up gamepads during playtime, it cannot
193
+ be assumed that these state objects will always be the same. Thus
194
+ they should be accessed directly from this `State` table anytime they
195
+ need to be used.
196
+ :::
197
+
198
+ ```lua
199
+ local leftThumbstick = gamepad.State[Enum.KeyCode.Thumbstick1]
200
+ print(leftThumbstick.Position)
201
+ -- It would be better to use gamepad:GetThumbstick(Enum.KeyCode.Thumbstick1),
202
+ -- but this is just an example of direct state access.
203
+ ```
204
+ ]=]
205
+
206
+ --[=[
207
+ @within Gamepad
208
+ @type GamepadState {[Enum.KeyCode]: InputObject}
209
+ ]=]
210
+
211
+ --[=[
212
+ @param gamepad Enum.UserInputType?
213
+ @return Gamepad
214
+ Constructs a gamepad object.
215
+
216
+ If no gamepad UserInputType is provided, this object will always wrap
217
+ around the currently-active gamepad, even if it changes. In most cases
218
+ where input is needed from just the primary gamepad used by the player,
219
+ leaving the `gamepad` argument blank is preferred.
220
+
221
+ Only include the `gamepad` argument when it is necessary to hard-lock
222
+ the object to a specific gamepad input type.
223
+
224
+ ```lua
225
+ -- In most cases, construct the gamepad as such:
226
+ local gamepad = Gamepad.new()
227
+
228
+ -- If the exact UserInputType gamepad is needed, pass it as such:
229
+ local gamepad = Gamepad.new(Enum.UserInputType.Gamepad1)
230
+ ```
231
+ ]=]
232
+ function Gamepad.new(gamepad: Enum.UserInputType?)
233
+ local self = setmetatable({}, Gamepad)
234
+
235
+ self._trove = Trove.new()
236
+ self._gamepadTrove = self._trove:Construct(Trove)
237
+ self.ButtonDown = self._trove:Construct(Signal)
238
+ self.ButtonUp = self._trove:Construct(Signal)
239
+ self.Connected = self._trove:Construct(Signal)
240
+ self.Disconnected = self._trove:Construct(Signal)
241
+ self.GamepadChanged = self._trove:Construct(Signal)
242
+ self.DefaultDeadzone = 0.05
243
+ self.SupportsVibration = false
244
+ self.State = {}
245
+
246
+ self:_setupGamepad(gamepad)
247
+ self:_setupMotors()
248
+
249
+ return self
250
+ end
251
+
252
+ function Gamepad:_setupActiveGamepad(gamepad: Enum.UserInputType?)
253
+ local lastGamepad = self._gamepad
254
+ if gamepad == lastGamepad then
255
+ return
256
+ end
257
+
258
+ self._gamepadTrove:Clean()
259
+ table.clear(self.State)
260
+ self.SupportsVibration = if gamepad then HapticService:IsVibrationSupported(gamepad) else false
261
+
262
+ self._gamepad = gamepad
263
+
264
+ -- Stop if disconnected:
265
+ if not gamepad then
266
+ self.Disconnected:Fire()
267
+ self.GamepadChanged:Fire(nil)
268
+ return
269
+ end
270
+
271
+ for _, inputObject in UserInputService:GetGamepadState(gamepad) do
272
+ self.State[inputObject.KeyCode] = inputObject
273
+ end
274
+
275
+ self._gamepadTrove:Add(self, "StopMotors")
276
+
277
+ self._gamepadTrove:Connect(UserInputService.InputBegan, function(input, processed)
278
+ if input.UserInputType == gamepad then
279
+ self.ButtonDown:Fire(input.KeyCode, processed)
280
+ end
281
+ end)
282
+
283
+ self._gamepadTrove:Connect(UserInputService.InputEnded, function(input, processed)
284
+ if input.UserInputType == gamepad then
285
+ self.ButtonUp:Fire(input.KeyCode, processed)
286
+ end
287
+ end)
288
+
289
+ if lastGamepad == nil then
290
+ self.Connected:Fire()
291
+ end
292
+ self.GamepadChanged:Fire(gamepad)
293
+ end
294
+
295
+ function Gamepad:_setupGamepad(forcedGamepad: Enum.UserInputType?)
296
+ if forcedGamepad then
297
+ -- Forced gamepad:
298
+
299
+ self._trove:Connect(UserInputService.GamepadConnected, function(gp)
300
+ if gp == forcedGamepad then
301
+ self:_setupActiveGamepad(forcedGamepad)
302
+ end
303
+ end)
304
+
305
+ self._trove:Connect(UserInputService.GamepadDisconnected, function(gp)
306
+ if gp == forcedGamepad then
307
+ self:_setupActiveGamepad(nil)
308
+ end
309
+ end)
310
+
311
+ if UserInputService:GetGamepadConnected(forcedGamepad) then
312
+ self:_setupActiveGamepad(forcedGamepad)
313
+ end
314
+ else
315
+ -- Dynamic gamepad:
316
+
317
+ local function CheckToSetupActive()
318
+ local active = GetActiveGamepad()
319
+ if active ~= self._gamepad then
320
+ self:_setupActiveGamepad(active)
321
+ end
322
+ end
323
+
324
+ self._trove:Connect(UserInputService.GamepadConnected, CheckToSetupActive)
325
+ self._trove:Connect(UserInputService.GamepadDisconnected, CheckToSetupActive)
326
+ self:_setupActiveGamepad(GetActiveGamepad())
327
+ end
328
+ end
329
+
330
+ function Gamepad:_setupMotors()
331
+ self._setMotorIds = {}
332
+ for _, motor in Enum.VibrationMotor:GetEnumItems() do
333
+ self._setMotorIds[motor] = 0
334
+ end
335
+ end
336
+
337
+ --[=[
338
+ @param thumbstick Enum.KeyCode
339
+ @param deadzoneThreshold number?
340
+ @return Vector2
341
+ Gets the position of the given thumbstick. The two thumbstick
342
+ KeyCodes are `Enum.KeyCode.Thumbstick1` and `Enum.KeyCode.Thumbstick2`.
343
+
344
+ If `deadzoneThreshold` is not included, the `DefaultDeadzone` value is
345
+ used instead.
346
+
347
+ ```lua
348
+ local leftThumbstick = gamepad:GetThumbstick(Enum.KeyCode.Thumbstick1)
349
+ print("Left thumbstick position", leftThumbstick)
350
+ ```
351
+ ]=]
352
+ function Gamepad:GetThumbstick(thumbstick: Enum.KeyCode, deadzoneThreshold: number?): Vector2
353
+ local pos = self.State[thumbstick].Position
354
+ local deadzone = deadzoneThreshold or self.DefaultDeadzone
355
+ return Vector2.new(ApplyDeadzone(pos.X, deadzone), ApplyDeadzone(pos.Y, deadzone))
356
+ end
357
+
358
+ --[=[
359
+ @param trigger KeyCode
360
+ @param deadzoneThreshold number?
361
+ @return number
362
+ Gets the position of the given trigger. The triggers are usually going
363
+ to be `Enum.KeyCode.ButtonL2` and `Enum.KeyCode.ButtonR2`. These trigger
364
+ buttons are analog, and will output a value between the range of [0, 1].
365
+
366
+ If `deadzoneThreshold` is not included, the `DefaultDeadzone` value is
367
+ used instead.
368
+
369
+ ```lua
370
+ local triggerAmount = gamepad:GetTrigger(Enum.KeyCode.ButtonR2)
371
+ print(triggerAmount)
372
+ ```
373
+ ]=]
374
+ function Gamepad:GetTrigger(trigger: Enum.KeyCode, deadzoneThreshold: number?): number
375
+ return ApplyDeadzone(self.State[trigger].Position.Z, deadzoneThreshold or self.DefaultDeadzone)
376
+ end
377
+
378
+ --[=[
379
+ @param gamepadButton KeyCode
380
+ @return boolean
381
+ Returns `true` if the given button is down. This includes
382
+ any button on the gamepad, such as `Enum.KeyCode.ButtonA`,
383
+ `Enum.KeyCode.ButtonL3`, `Enum.KeyCode.DPadUp`, etc.
384
+
385
+ ```lua
386
+ -- Check if the 'A' button is down:
387
+ if gamepad:IsButtonDown(Enum.KeyCode.ButtonA) then
388
+ print("ButtonA is down")
389
+ end
390
+ ```
391
+ ]=]
392
+ function Gamepad:IsButtonDown(gamepadButton: Enum.KeyCode): boolean
393
+ return UserInputService:IsGamepadButtonDown(self._gamepad, gamepadButton)
394
+ end
395
+
396
+ --[=[
397
+ @param motor Enum.VibrationMotor
398
+ @return boolean
399
+ Returns `true` if the given motor is supported.
400
+
401
+ ```lua
402
+ -- Pulse the trigger (e.g. shooting a weapon), but fall back to
403
+ -- the large motor if not supported:
404
+ local motor = Enum.VibrationMotor.Large
405
+ if gamepad:IsMotorSupported(Enum.VibrationMotor.RightTrigger) then
406
+ motor = Enum.VibrationMotor.RightTrigger
407
+ end
408
+ gamepad:PulseMotor(motor, 1, 0.1)
409
+ ```
410
+ ]=]
411
+ function Gamepad:IsMotorSupported(motor: Enum.VibrationMotor): boolean
412
+ return HapticService:IsMotorSupported(self._gamepad, motor)
413
+ end
414
+
415
+ --[=[
416
+ @param motor Enum.VibrationMotor
417
+ @param intensity number
418
+ Sets the gamepad's haptic motor to a certain intensity. The
419
+ intensity value is a number in the range of [0, 1].
420
+
421
+ ```lua
422
+ gamepad:SetMotor(Enum.VibrationMotor.Large, 0.5)
423
+ ```
424
+ ]=]
425
+ function Gamepad:SetMotor(motor: Enum.VibrationMotor, intensity: number): number
426
+ self._setMotorIds[motor] += 1
427
+ local id = self._setMotorIds[motor]
428
+ HapticService:SetMotor(self._gamepad, motor, intensity)
429
+
430
+ return id
431
+ end
432
+
433
+ --[=[
434
+ @param motor Enum.VibrationMotor
435
+ @param intensity number
436
+ @param duration number
437
+ Sets the gamepad's haptic motor to a certain intensity for a given
438
+ period of time. The motor will stop vibrating after the given
439
+ `duration` has elapsed.
440
+
441
+ Calling any motor setter methods (e.g. `SetMotor`, `PulseMotor`,
442
+ `StopMotor`) _after_ calling this method will override the pulse.
443
+ For instance, if `PulseMotor` is called, and then `SetMotor` is
444
+ called right afterwards, `SetMotor` will take precedent.
445
+
446
+ ```lua
447
+ -- Pulse the large motor for 0.2 seconds with an intensity of 90%:
448
+ gamepad:PulseMotor(Enum.VibrationMotor.Large, 0.9, 0.2)
449
+
450
+ -- Example of PulseMotor being overridden:
451
+ gamepad:PulseMotor(Enum.VibrationMotor.Large, 1, 3)
452
+ task.wait(0.1)
453
+ gamepad:SetMotor(Enum.VibrationMotor.Large, 0.5)
454
+ -- Now the pulse won't shut off the motor after 3 seconds,
455
+ -- because SetMotor was called, which cancels the pulse.
456
+ ```
457
+ ]=]
458
+ function Gamepad:PulseMotor(motor: Enum.VibrationMotor, intensity: number, duration: number)
459
+ local id = self:SetMotor(motor, intensity)
460
+
461
+ local heartbeat = HeartbeatDelay(duration, function()
462
+ if self._setMotorIds[motor] ~= id then
463
+ return
464
+ end
465
+ self:StopMotor(motor)
466
+ end)
467
+
468
+ self._gamepadTrove:Add(heartbeat)
469
+ end
470
+
471
+ --[=[
472
+ @param motor Enum.VibrationMotor
473
+ Stops the given motor. This is equivalent to calling
474
+ `gamepad:SetMotor(motor, 0)`.
475
+
476
+ ```lua
477
+ gamepad:SetMotor(Enum.VibrationMotor.Large, 1)
478
+ task.wait(0.1)
479
+ gamepad:StopMotor(Enum.VibrationMotor.Large)
480
+ ```
481
+ ]=]
482
+ function Gamepad:StopMotor(motor: Enum.VibrationMotor)
483
+ self:SetMotor(motor, 0)
484
+ end
485
+
486
+ --[=[
487
+ Stops all motors on the gamepad.
488
+
489
+ ```lua
490
+ gamepad:SetMotor(Enum.VibrationMotor.Large, 1)
491
+ gamepad:SetMotor(Enum.VibrationMotor.Small, 1)
492
+ task.wait(0.1)
493
+ gamepad:StopMotors()
494
+ ```
495
+ ]=]
496
+ function Gamepad:StopMotors()
497
+ for _, motor in Enum.VibrationMotor:GetEnumItems() do
498
+ if self:IsMotorSupported(motor) then
499
+ self:StopMotor(motor)
500
+ end
501
+ end
502
+ end
503
+
504
+ --[=[
505
+ @return boolean
506
+ Returns `true` if the gamepad is currently connected.
507
+ ]=]
508
+ function Gamepad:IsConnected(): boolean
509
+ return if self._gamepad then UserInputService:GetGamepadConnected(self._gamepad) else false
510
+ end
511
+
512
+ --[=[
513
+ @return Enum.UserInputType?
514
+ Gets the current gamepad UserInputType that the gamepad object
515
+ is using. This will be `nil` if there is no connected gamepad.
516
+ ]=]
517
+ function Gamepad:GetUserInputType(): Enum.UserInputType?
518
+ return self._gamepad
519
+ end
520
+
521
+ --[=[
522
+ @param enabled boolean
523
+ Sets the [`GuiService.AutoSelectGuiEnabled`](https://developer.roblox.com/en-us/api-reference/property/GuiService/AutoSelectGuiEnabled)
524
+ property.
525
+
526
+ This sets whether or not the Select button on a gamepad will try to auto-select
527
+ a GUI object on screen. This does _not_ turn on/off GUI gamepad navigation,
528
+ but just the initial selection using the Select button.
529
+
530
+ For UX purposes, it usually is preferred to set this to `false` and then
531
+ manually set the [`GuiService.SelectedObject`](https://developer.roblox.com/en-us/api-reference/property/GuiService/SelectedObject)
532
+ property within code to set the selected object for gamepads.
533
+
534
+ ```lua
535
+ gamepad:SetAutoSelectGui(false)
536
+ game:GetService("GuiService").SelectedObject = someGuiObject
537
+ ```
538
+ ]=]
539
+ function Gamepad:SetAutoSelectGui(enabled: boolean)
540
+ GuiService.AutoSelectGuiEnabled = enabled
541
+ end
542
+
543
+ --[=[
544
+ @return boolean
545
+ Returns the [`GuiService.AutoSelectGuiEnabled`](https://developer.roblox.com/en-us/api-reference/property/GuiService/AutoSelectGuiEnabled)
546
+ property.
547
+ ]=]
548
+ function Gamepad:IsAutoSelectGuiEnabled(): boolean
549
+ return GuiService.AutoSelectGuiEnabled
550
+ end
551
+
552
+ --[=[
553
+ Destroys the gamepad object.
554
+ ]=]
555
+ function Gamepad:Destroy()
556
+ self._trove:Destroy()
557
+ end
558
+
559
+ return Gamepad