@boyangsicwastaken/muchachohitbox 1.0.0

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.
package/index.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ interface SignalConnection {
2
+ Connected: boolean;
3
+ Disconnect(): void;
4
+ Destroy(): void;
5
+ }
6
+
7
+ interface Signal<T extends unknown[]> {
8
+ Fire(...args: T): void;
9
+ FireDeferred(...args: T): void;
10
+ Connect(fn: (...args: T) => void): SignalConnection;
11
+ Once(fn: (...args: T) => void): SignalConnection;
12
+ DisconnectAll(): void;
13
+ GetConnections(): SignalConnection[];
14
+ Destroy(): void;
15
+ Wait(): LuaTuple<T>;
16
+ }
17
+
18
+ type DetectionMode = "Default" | "ConstantDetection" | "HitOnce" | "HitParts";
19
+
20
+ interface Hitbox {
21
+ Visualizer: boolean;
22
+ VisualizerColor: Color3;
23
+ VisualizerTransparency: number;
24
+
25
+ DetectionMode: DetectionMode;
26
+ AutoDestroy: boolean;
27
+ Key: string;
28
+
29
+ OverlapParams: OverlapParams;
30
+
31
+ Size: Vector3 | number;
32
+ Shape: Enum.PartType;
33
+ CFrame: CFrame | BasePart;
34
+ Offset: CFrame;
35
+
36
+ VelocityPrediction: boolean;
37
+ VelocityPredictionTime: number;
38
+
39
+ Touched: Signal<[hit: BasePart, humanoid: Humanoid | undefined]>;
40
+ TouchEnded: Signal<[part: BasePart]>;
41
+
42
+ Start(): void;
43
+ Stop(): void;
44
+ Destroy(): void;
45
+ }
46
+
47
+ interface MuchachoHitbox {
48
+ CreateHitbox(): Hitbox;
49
+ FindHitbox(key: string): Hitbox | undefined;
50
+ }
51
+
52
+ declare const MuchachoHitbox: MuchachoHitbox;
53
+ export = MuchachoHitbox;
@@ -0,0 +1,20 @@
1
+ local module = {}
2
+
3
+ local function find(a, tbl)
4
+ for _, a_ in ipairs(tbl) do
5
+ if a_==a then return true end
6
+ end
7
+ end
8
+
9
+ function module.difference(a, b)
10
+ local ret = {}
11
+ for _, v in ipairs(a) do
12
+ if not find(v,b) then table.insert(ret, v) end
13
+ end
14
+
15
+ return ret
16
+ end
17
+
18
+
19
+ return module
20
+
@@ -0,0 +1,429 @@
1
+ -- -----------------------------------------------------------------------------
2
+ -- Batched Yield-Safe Signal Implementation --
3
+ -- This is a Signal class which has effectively identical behavior to a --
4
+ -- normal RBXScriptSignal, with the only difference being a couple extra --
5
+ -- stack frames at the bottom of the stack trace when an error is thrown. --
6
+ -- This implementation caches runner coroutines, so the ability to yield in --
7
+ -- the signal handlers comes at minimal extra cost over a naive signal --
8
+ -- implementation that either always or never spawns a thread. --
9
+ -- --
10
+ -- License: --
11
+ -- Licensed under the MIT license. --
12
+ -- --
13
+ -- Authors: --
14
+ -- stravant - July 31st, 2021 - Created the file. --
15
+ -- sleitnick - August 3rd, 2021 - Modified for Knit. --
16
+ -- -----------------------------------------------------------------------------
17
+
18
+ -- Signal types
19
+ export type Connection = {
20
+ Disconnect: (self: Connection) -> (),
21
+ Destroy: (self: Connection) -> (),
22
+ Connected: boolean,
23
+ }
24
+
25
+ export type Signal<T...> = {
26
+ Fire: (self: Signal<T...>, T...) -> (),
27
+ FireDeferred: (self: Signal<T...>, T...) -> (),
28
+ Connect: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
29
+ Once: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
30
+ DisconnectAll: (self: Signal<T...>) -> (),
31
+ GetConnections: (self: Signal<T...>) -> { Connection },
32
+ Destroy: (self: Signal<T...>) -> (),
33
+ Wait: (self: Signal<T...>) -> T...,
34
+ }
35
+
36
+ -- The currently idle thread to run the next handler on
37
+ local freeRunnerThread = nil
38
+
39
+ -- Function which acquires the currently idle handler runner thread, runs the
40
+ -- function fn on it, and then releases the thread, returning it to being the
41
+ -- currently idle one.
42
+ -- If there was a currently idle runner thread already, that's okay, that old
43
+ -- one will just get thrown and eventually GCed.
44
+ local function acquireRunnerThreadAndCallEventHandler(fn, ...)
45
+ local acquiredRunnerThread = freeRunnerThread
46
+ freeRunnerThread = nil
47
+ fn(...)
48
+ -- The handler finished running, this runner thread is free again.
49
+ freeRunnerThread = acquiredRunnerThread
50
+ end
51
+
52
+ -- Coroutine runner that we create coroutines of. The coroutine can be
53
+ -- repeatedly resumed with functions to run followed by the argument to run
54
+ -- them with.
55
+ local function runEventHandlerInFreeThread(...)
56
+ acquireRunnerThreadAndCallEventHandler(...)
57
+ while true do
58
+ acquireRunnerThreadAndCallEventHandler(coroutine.yield())
59
+ end
60
+ end
61
+
62
+ --[=[
63
+ @within Signal
64
+ @interface SignalConnection
65
+ .Connected boolean
66
+ .Disconnect (SignalConnection) -> ()
67
+
68
+ Represents a connection to a signal.
69
+ ```lua
70
+ local connection = signal:Connect(function() end)
71
+ print(connection.Connected) --> true
72
+ connection:Disconnect()
73
+ print(connection.Connected) --> false
74
+ ```
75
+ ]=]
76
+
77
+ -- Connection class
78
+ local Connection = {}
79
+ Connection.__index = Connection
80
+
81
+ function Connection:Disconnect()
82
+ if not self.Connected then
83
+ return
84
+ end
85
+ self.Connected = false
86
+
87
+ -- Unhook the node, but DON'T clear it. That way any fire calls that are
88
+ -- currently sitting on this node will be able to iterate forwards off of
89
+ -- it, but any subsequent fire calls will not hit it, and it will be GCed
90
+ -- when no more fire calls are sitting on it.
91
+ if self._signal._handlerListHead == self then
92
+ self._signal._handlerListHead = self._next
93
+ else
94
+ local prev = self._signal._handlerListHead
95
+ while prev and prev._next ~= self do
96
+ prev = prev._next
97
+ end
98
+ if prev then
99
+ prev._next = self._next
100
+ end
101
+ end
102
+ end
103
+
104
+ Connection.Destroy = Connection.Disconnect
105
+
106
+ -- Make Connection strict
107
+ setmetatable(Connection, {
108
+ __index = function(_tb, key)
109
+ error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2)
110
+ end,
111
+ __newindex = function(_tb, key, _value)
112
+ error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2)
113
+ end,
114
+ })
115
+
116
+ --[=[
117
+ @within Signal
118
+ @type ConnectionFn (...any) -> ()
119
+
120
+ A function connected to a signal.
121
+ ]=]
122
+
123
+ --[=[
124
+ @class Signal
125
+
126
+ A Signal is a data structure that allows events to be dispatched
127
+ and observed.
128
+
129
+ This implementation is a direct copy of the de facto standard, [GoodSignal](https://devforum.roblox.com/t/lua-signal-class-comparison-optimal-goodsignal-class/1387063),
130
+ with some added methods and typings.
131
+
132
+ For example:
133
+ ```lua
134
+ local signal = Signal.new()
135
+
136
+ -- Subscribe to a signal:
137
+ signal:Connect(function(msg)
138
+ print("Got message:", msg)
139
+ end)
140
+
141
+ -- Dispatch an event:
142
+ signal:Fire("Hello world!")
143
+ ```
144
+ ]=]
145
+ local Signal = {}
146
+ Signal.__index = Signal
147
+
148
+ --[=[
149
+ Constructs a new Signal
150
+
151
+ @return Signal
152
+ ]=]
153
+ function Signal.new<T...>(): Signal<T...>
154
+ local self = setmetatable({
155
+ _handlerListHead = false,
156
+ _proxyHandler = nil,
157
+ _yieldedThreads = nil,
158
+ }, Signal)
159
+
160
+ return self
161
+ end
162
+
163
+ --[=[
164
+ Constructs a new Signal that wraps around an RBXScriptSignal.
165
+
166
+ @param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap
167
+ @return Signal
168
+
169
+ For example:
170
+ ```lua
171
+ local signal = Signal.Wrap(workspace.ChildAdded)
172
+ signal:Connect(function(part) print(part.Name .. " added") end)
173
+ Instance.new("Part").Parent = workspace
174
+ ```
175
+ ]=]
176
+ function Signal.Wrap<T...>(rbxScriptSignal: RBXScriptSignal): Signal<T...>
177
+ assert(
178
+ typeof(rbxScriptSignal) == "RBXScriptSignal",
179
+ "Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal)
180
+ )
181
+
182
+ local signal = Signal.new()
183
+ signal._proxyHandler = rbxScriptSignal:Connect(function(...)
184
+ signal:Fire(...)
185
+ end)
186
+
187
+ return signal
188
+ end
189
+
190
+ --[=[
191
+ Checks if the given object is a Signal.
192
+
193
+ @param obj any -- Object to check
194
+ @return boolean -- `true` if the object is a Signal.
195
+ ]=]
196
+ function Signal.Is(obj: any): boolean
197
+ return type(obj) == "table" and getmetatable(obj) == Signal
198
+ end
199
+
200
+ --[=[
201
+ @param fn ConnectionFn
202
+ @return SignalConnection
203
+
204
+ Connects a function to the signal, which will be called anytime the signal is fired.
205
+ ```lua
206
+ signal:Connect(function(msg, num)
207
+ print(msg, num)
208
+ end)
209
+
210
+ signal:Fire("Hello", 25)
211
+ ```
212
+ ]=]
213
+ function Signal:Connect(fn)
214
+ local connection = setmetatable({
215
+ Connected = true,
216
+ _signal = self,
217
+ _fn = fn,
218
+ _next = false,
219
+ }, Connection)
220
+
221
+ if self._handlerListHead then
222
+ connection._next = self._handlerListHead
223
+ self._handlerListHead = connection
224
+ else
225
+ self._handlerListHead = connection
226
+ end
227
+
228
+ return connection
229
+ end
230
+
231
+ --[=[
232
+ @deprecated v1.3.0 -- Use `Signal:Once` instead.
233
+ @param fn ConnectionFn
234
+ @return SignalConnection
235
+ ]=]
236
+ function Signal:ConnectOnce(fn)
237
+ return self:Once(fn)
238
+ end
239
+
240
+ --[=[
241
+ @param fn ConnectionFn
242
+ @return SignalConnection
243
+
244
+ Connects a function to the signal, which will be called the next time the signal fires. Once
245
+ the connection is triggered, it will disconnect itself.
246
+ ```lua
247
+ signal:Once(function(msg, num)
248
+ print(msg, num)
249
+ end)
250
+
251
+ signal:Fire("Hello", 25)
252
+ signal:Fire("This message will not go through", 10)
253
+ ```
254
+ ]=]
255
+ function Signal:Once(fn)
256
+ local connection
257
+ local done = false
258
+
259
+ connection = self:Connect(function(...)
260
+ if done then
261
+ return
262
+ end
263
+
264
+ done = true
265
+ connection:Disconnect()
266
+ fn(...)
267
+ end)
268
+
269
+ return connection
270
+ end
271
+
272
+ function Signal:GetConnections()
273
+ local items = {}
274
+
275
+ local item = self._handlerListHead
276
+ while item do
277
+ table.insert(items, item)
278
+ item = item._next
279
+ end
280
+
281
+ return items
282
+ end
283
+
284
+ -- Disconnect all handlers. Since we use a linked list it suffices to clear the
285
+ -- reference to the head handler.
286
+ --[=[
287
+ Disconnects all connections from the signal.
288
+ ```lua
289
+ signal:DisconnectAll()
290
+ ```
291
+ ]=]
292
+ function Signal:DisconnectAll()
293
+ local item = self._handlerListHead
294
+ while item do
295
+ item.Connected = false
296
+ item = item._next
297
+ end
298
+ self._handlerListHead = false
299
+
300
+ local yieldedThreads = rawget(self, "_yieldedThreads")
301
+ if yieldedThreads then
302
+ for thread in yieldedThreads do
303
+ if coroutine.status(thread) == "suspended" then
304
+ warn(debug.traceback(thread, "signal disconnected; yielded thread cancelled", 2))
305
+ task.cancel(thread)
306
+ end
307
+ end
308
+ table.clear(self._yieldedThreads)
309
+ end
310
+ end
311
+
312
+ -- Signal:Fire(...) implemented by running the handler functions on the
313
+ -- coRunnerThread, and any time the resulting thread yielded without returning
314
+ -- to us, that means that it yielded to the Roblox scheduler and has been taken
315
+ -- over by Roblox scheduling, meaning we have to make a new coroutine runner.
316
+ --[=[
317
+ @param ... any
318
+
319
+ Fire the signal, which will call all of the connected functions with the given arguments.
320
+ ```lua
321
+ signal:Fire("Hello")
322
+
323
+ -- Any number of arguments can be fired:
324
+ signal:Fire("Hello", 32, {Test = "Test"}, true)
325
+ ```
326
+ ]=]
327
+ function Signal:Fire(...)
328
+ local item = self._handlerListHead
329
+ while item do
330
+ if item.Connected then
331
+ if not freeRunnerThread then
332
+ freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
333
+ end
334
+ task.spawn(freeRunnerThread, item._fn, ...)
335
+ end
336
+ item = item._next
337
+ end
338
+ end
339
+
340
+ --[=[
341
+ @param ... any
342
+
343
+ Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse.
344
+ ```lua
345
+ signal:FireDeferred("Hello")
346
+ ```
347
+ ]=]
348
+ function Signal:FireDeferred(...)
349
+ local item = self._handlerListHead
350
+ while item do
351
+ local conn = item
352
+ task.defer(function(...)
353
+ if conn.Connected then
354
+ conn._fn(...)
355
+ end
356
+ end, ...)
357
+ item = item._next
358
+ end
359
+ end
360
+
361
+ --[=[
362
+ @return ... any
363
+ @yields
364
+
365
+ Yields the current thread until the signal is fired, and returns the arguments fired from the signal.
366
+ Yielding the current thread is not always desirable. If the desire is to only capture the next event
367
+ fired, using `Once` might be a better solution.
368
+ ```lua
369
+ task.spawn(function()
370
+ local msg, num = signal:Wait()
371
+ print(msg, num) --> "Hello", 32
372
+ end)
373
+ signal:Fire("Hello", 32)
374
+ ```
375
+ ]=]
376
+ function Signal:Wait()
377
+ local yieldedThreads = rawget(self, "_yieldedThreads")
378
+ if not yieldedThreads then
379
+ yieldedThreads = {}
380
+ rawset(self, "_yieldedThreads", yieldedThreads)
381
+ end
382
+
383
+ local thread = coroutine.running()
384
+ yieldedThreads[thread] = true
385
+
386
+ self:Once(function(...)
387
+ yieldedThreads[thread] = nil
388
+ task.spawn(thread, ...)
389
+ end)
390
+
391
+ return coroutine.yield()
392
+ end
393
+
394
+ --[=[
395
+ Cleans up the signal.
396
+
397
+ Technically, this is only necessary if the signal is created using
398
+ `Signal.Wrap`. Connections should be properly GC'd once the signal
399
+ is no longer referenced anywhere. However, it is still good practice
400
+ to include ways to strictly clean up resources. Calling `Destroy`
401
+ on a signal will also disconnect all connections immediately.
402
+ ```lua
403
+ signal:Destroy()
404
+ ```
405
+ ]=]
406
+ function Signal:Destroy()
407
+ self:DisconnectAll()
408
+
409
+ local proxyHandler = rawget(self, "_proxyHandler")
410
+ if proxyHandler then
411
+ proxyHandler:Disconnect()
412
+ end
413
+ end
414
+
415
+ -- Make signal strict
416
+ setmetatable(Signal, {
417
+ __index = function(_tb, key)
418
+ error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2)
419
+ end,
420
+ __newindex = function(_tb, key, _value)
421
+ error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2)
422
+ end,
423
+ })
424
+
425
+ return table.freeze({
426
+ new = Signal.new,
427
+ Wrap = Signal.Wrap,
428
+ Is = Signal.Is,
429
+ })
package/out/Types.luau ADDED
@@ -0,0 +1,61 @@
1
+ local Goodsignal = require(script.Parent.GoodSignal)
2
+
3
+ local types = {}
4
+
5
+ export type HitboxProperties = {
6
+ Visualizer: boolean,
7
+ DetectionMode: ("Default" | "ConstantDetection" | "HitOnce" | "HitParts"),
8
+ AutoDestroy: boolean,
9
+ Key: string,
10
+
11
+ OverlapParams: OverlapParams,
12
+
13
+ Size: Vector3,
14
+ Shape: Enum.PartType,
15
+ CFrame: CFrame,
16
+ Offset: CFrame,
17
+
18
+ VelocityPredictionTime: number?,
19
+ VelocityPrediction: boolean?,
20
+
21
+ Touched: Goodsignal.Signal<BasePart, Humanoid?>,
22
+ TouchEnded: Goodsignal.Signal<BasePart, Humanoid?>,
23
+ } & any
24
+
25
+ export type Hitbox = {
26
+ -- properties
27
+ Visualizer: boolean,
28
+ VisualizerColor: Color3?,
29
+ VisualizerTransparency: number,
30
+
31
+ DetectionMode: ("Default" | "ConstantDetection" | "HitOnce" | "HitParts"),
32
+ AutoDestroy: boolean,
33
+ Key: string,
34
+
35
+ OverlapParams: OverlapParams,
36
+
37
+ Size: Vector3,
38
+ Shape: Enum.PartType,
39
+ CFrame: CFrame,
40
+ Offset: CFrame,
41
+
42
+ VelocityPredictionTime: number?,
43
+ VelocityPrediction: boolean?,
44
+
45
+ -- events
46
+ Touched: Goodsignal.Signal<BasePart, Humanoid?>,
47
+ TouchEnded: Goodsignal.Signal<BasePart, Humanoid?>,
48
+
49
+ -- methods
50
+ Start: (self: Hitbox) -> (),
51
+ Stop: (self: Hitbox) -> (),
52
+ Destroy: (self: Hitbox) -> (boolean),
53
+
54
+ -- dev
55
+ HitList: {Model}?,
56
+ TouchingParts: {BasePart}?,
57
+ Connection: RBXScriptConnection?,
58
+ Box: BoxHandleAdornment? | SphereHandleAdornment?,
59
+ } & any
60
+
61
+ return types
package/out/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/out/init.luau ADDED
@@ -0,0 +1,526 @@
1
+ --!strict
2
+
3
+ --[[
4
+
5
+
6
+ _____ __ _ __ ___ __
7
+ / ___/__ _______/ /_ (_) |/ /___ ______/ /____ _____
8
+ \__ \/ / / / ___/ __ \/ / /|_/ / __ `/ ___/ __/ _ \/ ___/
9
+ ___/ / /_/ (__ ) / / / / / / / /_/ (__ ) /_/ __/ /
10
+ /____/\__,_/____/_/ /_/_/_/ /_/\__,_/____/\__/\___/_/
11
+
12
+
13
+
14
+
15
+
16
+ ____________________________________________________________________________________________________________________________________________________________________________
17
+
18
+ [ UPDATE LOG v1.1 :]
19
+ 1. New property!!
20
+ Hitbox.Key = "insert anything you want here"
21
+ --- This property will be used for the new function | module:FindHitbox(key)
22
+
23
+ 2. New function!!
24
+ Module:FindHitbox(Key)
25
+ --- Returns a hitbox using specified Key, nil otherwise
26
+
27
+ 3. New detection mode! | "ConstantDetection"
28
+ Hitbox.DetectionMode = "ConstantDetection"
29
+ --- The same as the default detection mode but no hit pool / debounce
30
+ --- You're free to customize the debounce anyway you want
31
+
32
+ 4, Made the scripts cleaner
33
+ ____________________________________________________________________________________________________________________________________________________________________________
34
+
35
+ [ UPDATE LOG v1.2 :]
36
+ 1. Made the code better
37
+
38
+ ____________________________________________________________________________________________________________________________________________________________________________
39
+
40
+ [ UPDATE LOG v1.3 :]
41
+ 1. New property
42
+ HitboxObject.AutoDestroy = true (Default)
43
+ --- With the value being false you can keep using Stop()
44
+ and Start() without the hitbox being destroyed.
45
+
46
+ 2. New metamethod
47
+ HitboxObject:Destroy()
48
+ --- This destroys the hitbox. You only need to use this
49
+ When having AutoDestroy's value set to false.
50
+
51
+ 3. Minor bug fixes
52
+
53
+ ____________________________________________________________________________________________________________________________________________________________________________
54
+
55
+ [ UPDATE LOG v1.4 Experimental:]
56
+ 1. New event
57
+ HitboxObject.TouchEnded:Connect(instance)
58
+ Description
59
+ --- The event fires once a part stops touching the hitbox
60
+ Arguments
61
+ --- Instance part: Returns the part that the hitbox stopped touching
62
+
63
+ ____________________________________________________________________________________________________________________________________________________________________________
64
+
65
+ UPDATE LOG v1.5 Stable:]
66
+ 1. Reverted touch ended, will add back after the bug is fixed
67
+
68
+ ____________________________________________________________________________________________________________________________________________________________________________
69
+
70
+ UPDATE LOG v1.6 Stable:]
71
+ 1. Touch ended is back! It has been fixed
72
+ 2. HitboxObject.Key is now generated automatically
73
+ 3. Minor changes
74
+
75
+ ____________________________________________________________________________________________________________________________________________________________________________
76
+
77
+ UPDATE LOG v2.0 Experimental:]
78
+ 1. Added VelocityPrediction and VelocityPredictionTime property
79
+ 2. You can now set the color and transparency of a hitbox
80
+ 2. Minor fixes
81
+ 3. Code now uses type checking
82
+ ____________________________________________________________________________________________________________________________________________________________________________
83
+
84
+ Example code:
85
+ local module = require(game.ServerStorage.MuchachoHitbox)
86
+
87
+ local hitbox = module.CreateHitbox()
88
+ hitbox.Size = Vector3.new(10,10,10)
89
+ hitbox.CFrame = workspace.Part
90
+
91
+ -- IF YOU WANT TO ADD VELOCITY PREDICTION
92
+ hitbox.VelocityPrediction = true
93
+ hitbox.VelocityPredictionTime = .2
94
+
95
+ hitbox.Touched:Connect(function(hit, hum)
96
+ print(hit)
97
+ hum:TakeDamage(10)
98
+ end)
99
+
100
+ hitbox:Start()
101
+
102
+
103
+ Alright thats all for the example code, its a pretty simple module, you could make a module similar to this yourself.
104
+ And maybe even make it better.
105
+
106
+ If you encounter any bugs, please tell me in the comment section, or you could DM me on discord
107
+ sushimaster#7840
108
+
109
+ ❤ SushiMaster
110
+ ____________________________________________________________________________________________________________________________________________________________________________
111
+
112
+
113
+ [MuchachoHitbox Documentation]
114
+
115
+ * local Module = require(MuchachoHitbox)
116
+ --- Require the module
117
+
118
+
119
+ [ FUNCTIONS ]
120
+
121
+ * Module.CreateHitbox()
122
+ Description
123
+ --- Creates a hitbox
124
+
125
+ * Module:FindHitbox(Key)
126
+ Description
127
+ --- Returns a hitbox with specified Key
128
+
129
+ * HitboxObject:Start()
130
+ Description
131
+ --- Starts the hitbox.
132
+
133
+ * HitboxObject:Stop()
134
+ Description
135
+ --- Stops the hitbox and resets the debounce.
136
+
137
+ * HitboxObject:Destroy()
138
+ Description
139
+ --- Destroys the hitbox. Use this when you have
140
+ HitboxObject.AutoDestroy set to false
141
+
142
+ [ EVENTS ]
143
+
144
+ * HitboxObject.Touched:Connect(hit, humanoid)
145
+ Description
146
+ --- If the hitbox touches a humanoid, it'll return information on them
147
+ --- The hitbox can detect parts depending on the detection mode
148
+ Arguments
149
+ --- Instance part: Returns the part that the hitbox hit first
150
+ --- Instance humanoid: Returns the Humanoid object
151
+
152
+ * HitboxObject.TouchEnded:Connect(instance)
153
+ Description
154
+ --- The event fires once a part stops touching the hitbox
155
+ Arguments
156
+ --- Instance part: Returns the part that the hitbox stopped touching
157
+
158
+ [ PROPERTIES ]
159
+
160
+ * HitboxObject.OverlapParams: OverlapParams
161
+ Description
162
+ --- Takes in a OverlapParams object
163
+
164
+ * HitboxObject.Visualizer: boolean
165
+ Description
166
+ --- Turns on or off the visualizer part
167
+
168
+ * HitboxObject.CFrame: CFrame / Instance
169
+ Description
170
+ --- Sets the hitbox CFrame to the CFrame
171
+ --- If its an instance, then the hitbox would follow the instance
172
+
173
+ * HitboxObject.Shape: Enum.PartType.Block / Enum.PartType.Ball
174
+ Description
175
+ --- Defaults to block
176
+ --- Sets the hitbox shape to the property
177
+
178
+ * HitboxObject.Size: Vector3 / number
179
+ Description
180
+ --- Sets the size of the hitbox
181
+ --- It uses Vector3 if the shape is block
182
+ --- It uses number if the shape is ball
183
+
184
+ * HitboxObject.Offset: CFrame
185
+ Description
186
+ --- Hitbox offset
187
+
188
+ * HitboxObject.DetectionMode: string | "Default" , "HitOnce" , "HitParts" , "ConstantDetection"
189
+ Description
190
+ --- Default value set to "Default"
191
+ --- Changes on how the detection works
192
+
193
+ * HitboxObject.Key: String
194
+ Description
195
+ --- The key property for the find hitbox function
196
+ --- MuchachoHitbox automatically generates a randomized key for you but you can change it. The module will save the hitbox, and can be found using | Module:FindHitbox(Key)
197
+
198
+ * HitboxObject.AutoDestroy: boolean
199
+ Description
200
+ --- Default value is set to true
201
+ --- When set to true, :Stop() atomatically destroys the hitbox.
202
+ --- Does not destroy the hitbox when set to false. You'll
203
+ have to use :Destroy() to delete the hitbox.
204
+
205
+ * HitboxObject.VelocityPrediction: boolean
206
+ Description
207
+ --- Default value is set to false
208
+ --- When set to true, hitbox automatically predicts the velocity of the CFrame property if it is an instance. By "VelocityPredictionTime" amount of time
209
+
210
+ * HitboxObject.VelocityPredictionTime: number
211
+ Description
212
+ --- Default value is set to 0.1
213
+ --- When "VelocityPrediction" is set to true, this property determines how far in the future the hitbox will check for parts.
214
+
215
+ * HitboxObject.VisualizerColor: Color3
216
+ Description
217
+ --- Sets the color of the visualizer part
218
+
219
+ * HitboxObject.VisualizerTransparency: number
220
+ Description
221
+ --- Sets the transparency of the visualizer part
222
+
223
+ [ DETECTION MODES ]
224
+
225
+ * Default
226
+ Description
227
+ --- Checks if a humanoid exists when this hitbox touches a part. The hitbox will not return humanoids it has already hit for the duration
228
+ --- the hitbox has been active.
229
+
230
+ * HitParts
231
+ Description
232
+ --- OnHit will return every hit part, regardless if it's ascendant has a humanoid or not.
233
+ --- OnHit will no longer return a humanoid so you will have to check it. The hitbox will not return parts it has already hit for the
234
+ --- duration the hitbox has been active.
235
+
236
+ * HitOnce
237
+ Description
238
+ --- Hitbox will stop as soon as it detects a humanoid
239
+
240
+ * ConstantDetection
241
+ Description
242
+ --- The default detection mode but no hitlist / debounce
243
+
244
+ ____________________________________________________________________________________________________________________________________________________________________________
245
+
246
+ ]]
247
+ local rs = game:GetService("RunService")
248
+ local hs = game:GetService("HttpService")
249
+
250
+ local GoodSignal = require(script.GoodSignal)
251
+ local DictDiff = require(script.DictDiff)
252
+ local Types = require(script.Types)
253
+
254
+ local muchacho_hitbox = {}
255
+ muchacho_hitbox.__index = muchacho_hitbox
256
+
257
+ local adornment_form = {
258
+ ["Proportion"] = {
259
+ [Enum.PartType.Ball] = "Radius",
260
+ [Enum.PartType.Block] = "Size",
261
+ },
262
+
263
+ ["Shape"] = {
264
+ [Enum.PartType.Ball] = "SphereHandleAdornment",
265
+ [Enum.PartType.Block] = "BoxHandleAdornment",
266
+ },
267
+ }
268
+
269
+ local get_CFrame = {
270
+ ["Instance"] = function(point)
271
+ return point.CFrame
272
+ end,
273
+
274
+ ["CFrame"] = function(point)
275
+ return point
276
+ end,
277
+ }
278
+
279
+
280
+ local hitboxes = {}
281
+
282
+ -- public functions
283
+ function muchacho_hitbox.CreateHitbox()
284
+ local self = setmetatable({}, muchacho_hitbox) :: Types.Hitbox
285
+ self.DetectionMode = "Default"
286
+ self.AutoDestroy = true
287
+
288
+ self.Visualizer = true
289
+ self.VisualizerColor = Color3.fromRGB(255,0,0)
290
+ self.VisualizerTransparency = .8
291
+
292
+ self.VelocityPrediction = false
293
+ self.VelocityPredictionTime = 0.1
294
+
295
+ self.OverlapParams = OverlapParams.new()
296
+
297
+ self.Size = Vector3.new(0,0,0)
298
+ self.Shape = Enum.PartType.Block
299
+ self.CFrame = CFrame.new(0,0,0)
300
+ self.Offset = CFrame.new(0,0,0)
301
+
302
+ self.Key = hs:GenerateGUID(false)
303
+
304
+ self.HitList = {}
305
+ self.TouchingParts = {}
306
+
307
+ self.Touched = GoodSignal.new()
308
+ self.TouchEnded = GoodSignal.new()
309
+
310
+ return self
311
+ end
312
+
313
+ function muchacho_hitbox:FindHitbox(key) -- deprecated
314
+ if hitboxes[key] then
315
+ return hitboxes[key]
316
+ else
317
+ return nil
318
+ end
319
+ end
320
+
321
+ -- public methods
322
+ function muchacho_hitbox.Start(self: Types.Hitbox)
323
+ if hitboxes[self.Key] then
324
+ error("A hitbox with this Key has already been started. Change the key if you want to start this hitbox.")
325
+ end
326
+
327
+ hitboxes[self.Key] = self
328
+
329
+ -- looping the hitbox
330
+ task.spawn(function()
331
+ self._Connection = rs.Heartbeat:Connect(function()
332
+ self:_visualize()
333
+ self:_cast()
334
+ end)
335
+ end)
336
+ end
337
+
338
+ function muchacho_hitbox.Stop(self: Types.Hitbox)
339
+ local hitbox = muchacho_hitbox:FindHitbox(self.Key)
340
+
341
+ if not hitbox then
342
+ error("Hitbox has already been stopped")
343
+ end
344
+
345
+ -- clear hitbox
346
+ self:_clear()
347
+
348
+ if not self.AutoDestroy then return end
349
+
350
+ -- terminate hitbox
351
+ self.Touched:DisconnectAll()
352
+ self.TouchEnded:DisconnectAll()
353
+ --setmetatable(self, nil)
354
+ end
355
+
356
+ function muchacho_hitbox:Destroy()
357
+ local hitbox: Types.Hitbox = muchacho_hitbox:FindHitbox(self.Key)
358
+
359
+ if not hitbox then
360
+ error("Hitbox has already been destroyed")
361
+ end
362
+
363
+ -- clear hitbox
364
+ self:_clear()
365
+
366
+ -- terminate hitbox
367
+ self.Touched:DisconnectAll()
368
+ self.TouchEnded:DisconnectAll()
369
+ --setmetatable(self, nil)
370
+ end
371
+
372
+
373
+ -- private methods
374
+ function muchacho_hitbox._CastSpatialQuery(self: Types.Hitbox) : {BasePart}?
375
+ local point_type: CFrame | string = typeof(self.CFrame)
376
+ local point_cframe: CFrame = self:_PredictVelocity() or get_CFrame[point_type](self.CFrame)
377
+
378
+ local parts
379
+ local hitboxCFrame: CFrame = point_cframe * self.Offset
380
+
381
+ if self.Shape == Enum.PartType.Block then
382
+ parts = workspace:GetPartBoundsInBox(hitboxCFrame, self.Size, self.OverlapParams)
383
+ elseif self.Shape == Enum.PartType.Ball then
384
+ parts = workspace:GetPartBoundsInRadius(hitboxCFrame.Position, self.Size, self.OverlapParams)
385
+ else
386
+ error("Part type: " .. self.Shape .. " isn't compatible with muchachoHitbox")
387
+ end
388
+
389
+ return parts
390
+ end
391
+
392
+ function muchacho_hitbox._cast(self: Types.Hitbox, part: BasePart)
393
+ local mode = self.DetectionMode
394
+ local parts = self:_CastSpatialQuery()
395
+
396
+ self:_FindTouchEnded(parts)
397
+
398
+ for _, hit in pairs(parts) do
399
+ local character: Model = hit:FindFirstAncestorOfClass("Model") or hit.Parent
400
+ local humanoid: Humanoid? = character:FindFirstChildOfClass("Humanoid")
401
+
402
+ -- detection mode
403
+ if mode == "Default" then
404
+ if humanoid and not table.find(self.HitList, humanoid) then
405
+ table.insert(self.HitList, humanoid)
406
+
407
+ self:_InsertTouchingParts(hit)
408
+
409
+ self.Touched:Fire(hit, humanoid)
410
+ end
411
+
412
+ elseif mode == "ConstantDetection" then
413
+
414
+ if humanoid then
415
+ self:_InsertTouchingParts(hit)
416
+
417
+ self.Touched:Fire(hit, humanoid)
418
+ end
419
+
420
+ elseif mode == "HitOnce" then
421
+
422
+ if humanoid then
423
+ self:_InsertTouchingParts(hit)
424
+
425
+ self.Touched:Fire(hit, humanoid)
426
+ self.TouchEnded:Fire(hit)
427
+
428
+ self:Destroy()
429
+ break
430
+ end
431
+
432
+ elseif mode == "HitParts" then
433
+ self:_InsertTouchingParts(hit)
434
+
435
+ self.Touched:Fire(hit, nil)
436
+
437
+ end
438
+ end
439
+ end
440
+
441
+ function muchacho_hitbox._visualize(self: Types.Hitbox)
442
+ if not self.Visualizer then return end
443
+
444
+ local predictedCFrame = self:_PredictVelocity()
445
+
446
+ local point_type: string = typeof(self.CFrame)
447
+ local point_cframe: CFrame = predictedCFrame or get_CFrame[point_type](self.CFrame)
448
+
449
+ local proportion = adornment_form.Proportion[self.Shape]
450
+
451
+ if not self._Box then
452
+ local newBox = Instance.new(adornment_form.Shape[self.Shape])
453
+ newBox.Name = "Visualizer"
454
+ newBox.Adornee = workspace.Terrain
455
+ newBox[proportion] = self.Size
456
+ newBox.CFrame = point_cframe * self.Offset
457
+ newBox.Color3 = self.VisualizerColor
458
+ newBox.Transparency = self.VisualizerTransparency
459
+ newBox.Parent = workspace.Terrain
460
+ self._Box = newBox
461
+ else
462
+ self._Box.CFrame = point_cframe * self.Offset
463
+ end
464
+ end
465
+
466
+ function muchacho_hitbox._PredictVelocity(self: Types.Hitbox): CFrame | nil
467
+ if self.VelocityPrediction then
468
+ local PredictionTime: number = self.VelocityPredictionTime
469
+ local part: BasePart = self.CFrame
470
+ local constant: number = 1/PredictionTime
471
+
472
+ if PredictionTime > 0 and typeof(part) == "Instance" then
473
+ --local velocityVector = part.CFrame:VectorToObjectSpace(part.AssemblyLinearVelocity) / constant
474
+ --local predictedCFrame = part.CFrame * CFrame.new(velocityVector)
475
+ local Velocity = part.AssemblyLinearVelocity --// Normally this would be their ping
476
+ local PredictedPosition = part.Position + Velocity * PredictionTime
477
+ local PredictedCFrame = CFrame.new(PredictedPosition) * (part.CFrame - part.Position)
478
+
479
+
480
+ return PredictedCFrame
481
+ end
482
+ end
483
+
484
+ return nil
485
+ end
486
+
487
+ function muchacho_hitbox:_clear()
488
+ self.HitList = {}
489
+
490
+ if self._Connection then
491
+ self._Connection:Disconnect()
492
+ end
493
+
494
+ if self.Key then
495
+ hitboxes[self.Key] = nil
496
+ end
497
+
498
+ if self._Box then
499
+ self._Box:Destroy()
500
+ self.Box = nil
501
+ end
502
+ end
503
+
504
+ function muchacho_hitbox._InsertTouchingParts(self: Types.Hitbox, part)
505
+ if table.find(self.TouchingParts, part) then return end
506
+
507
+ table.insert(self.TouchingParts, part)
508
+ end
509
+
510
+ function muchacho_hitbox._FindTouchEnded(self: Types.Hitbox, parts: {BasePart}?)
511
+ if #self.TouchingParts == 0 then return end
512
+
513
+ local mode = self.DetectionMode
514
+ local differences = DictDiff.difference(self.TouchingParts, parts)
515
+
516
+ if differences then
517
+ for _, diff in ipairs(differences) do
518
+ self.TouchEnded:Fire(diff)
519
+ table.remove(self.TouchingParts, table.find(self.TouchingParts, diff))
520
+ end
521
+ end
522
+ end
523
+
524
+
525
+
526
+ return muchacho_hitbox
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@boyangsicwastaken/muchachohitbox",
3
+ "version": "1.0.0",
4
+ "description": "typescript port for muchachohitbox module",
5
+ "main": "out/init.luau",
6
+ "scripts": {
7
+ "build": "rbxtsc",
8
+ "watch": "rbxtsc -w"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "types": "index.d.ts",
14
+ "files": [
15
+ "out",
16
+ "index.d.ts",
17
+ "!**/*.tsbuildinfo"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "devDependencies": {
23
+ "@rbxts/compiler-types": "^3.0.0-types.0",
24
+ "@rbxts/types": "^1.0.905",
25
+ "prettier": "^3.8.1",
26
+ "roblox-ts": "^3.0.0",
27
+ "typescript": "^5.9.3"
28
+ }
29
+ }