@rbxts/vfx-forge 2.2.2-ts.1

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 (42) hide show
  1. package/LICENSE +82 -0
  2. package/README.md +39 -0
  3. package/out/forge-vfx/effects/beam.luau +312 -0
  4. package/out/forge-vfx/effects/bezier.luau +392 -0
  5. package/out/forge-vfx/effects/camera_shake.luau +200 -0
  6. package/out/forge-vfx/effects/lightning.luau +1183 -0
  7. package/out/forge-vfx/effects/mesh.luau +466 -0
  8. package/out/forge-vfx/effects/particle.luau +64 -0
  9. package/out/forge-vfx/effects/randomizer.luau +110 -0
  10. package/out/forge-vfx/effects/screen.luau +61 -0
  11. package/out/forge-vfx/effects/shockwave_debris.luau +277 -0
  12. package/out/forge-vfx/effects/shockwave_line.luau +356 -0
  13. package/out/forge-vfx/effects/shockwave_ring.luau +252 -0
  14. package/out/forge-vfx/effects/sound.luau +311 -0
  15. package/out/forge-vfx/effects/spin.luau +88 -0
  16. package/out/forge-vfx/effects/tweener.luau +122 -0
  17. package/out/forge-vfx/emitters.luau +387 -0
  18. package/out/forge-vfx/index.d.ts +356 -0
  19. package/out/forge-vfx/init.luau +279 -0
  20. package/out/forge-vfx/mod/attributes.luau +227 -0
  21. package/out/forge-vfx/mod/color/Oklab.luau +93 -0
  22. package/out/forge-vfx/mod/color/sRGB.luau +71 -0
  23. package/out/forge-vfx/mod/common/bezier.luau +372 -0
  24. package/out/forge-vfx/mod/common/flipbook.luau +102 -0
  25. package/out/forge-vfx/mod/lerp.luau +210 -0
  26. package/out/forge-vfx/mod/logger.luau +20 -0
  27. package/out/forge-vfx/mod/shape.luau +207 -0
  28. package/out/forge-vfx/mod/tween.luau +161 -0
  29. package/out/forge-vfx/mod/utility.luau +707 -0
  30. package/out/forge-vfx/obj/Bezier.luau +268 -0
  31. package/out/forge-vfx/obj/ObjectCache.luau +289 -0
  32. package/out/forge-vfx/services/caches.luau +62 -0
  33. package/out/forge-vfx/services/effects.luau +234 -0
  34. package/out/forge-vfx/services/enabled_effects.luau +120 -0
  35. package/out/forge-vfx/services/texture_loader.luau +174 -0
  36. package/out/forge-vfx/types.luau +43 -0
  37. package/out/index.d.ts +3 -0
  38. package/out/init.luau +5 -0
  39. package/out/shake.d.ts +2 -0
  40. package/out/shake.luau +6 -0
  41. package/out/tsconfig.tsbuildinfo +1 -0
  42. package/package.json +63 -0
@@ -0,0 +1,122 @@
1
+ local attr = require(script.Parent.Parent.mod.attributes)
2
+ local lerp = require(script.Parent.Parent.mod.lerp)
3
+ local tween = require(script.Parent.Parent.mod.tween)
4
+ local types = require(script.Parent.Parent.types)
5
+ local utility = require(script.Parent.Parent.mod.utility)
6
+
7
+ local rng = Random.new()
8
+
9
+ local tweener = {}
10
+
11
+ function tweener.emit(obj: RayValue, scope: types.scope, isAttribute: boolean)
12
+ local target = utility.getTarget(obj)
13
+
14
+ if not target then
15
+ return
16
+ end
17
+
18
+ local name = obj.Name
19
+
20
+ local emitDelay = attr.get(obj, "EmitDelay", 0)
21
+ local resetOnFinish = attr.get(obj, "ResetOnFinish", true)
22
+
23
+ local durationRange = attr.getRange(obj, "Duration", NumberRange.new(1, 1), NumberRange.new(0, math.huge))
24
+
25
+ if emitDelay > 0 then
26
+ task.wait(emitDelay)
27
+ end
28
+
29
+ local endValue = attr.get(obj, "_END_VALUE", nil)
30
+ local overrideStartValue = attr.get(obj, "_START_VALUE", nil)
31
+
32
+ local startValue
33
+
34
+ if isAttribute then
35
+ startValue = attr.get(target, name, nil)
36
+ else
37
+ local ok, val = pcall(function()
38
+ return target[name]
39
+ end)
40
+
41
+ if not ok then
42
+ return
43
+ end
44
+
45
+ startValue = val
46
+ end
47
+
48
+ if overrideStartValue then
49
+ startValue = overrideStartValue
50
+ end
51
+
52
+ local typeName = typeof(startValue)
53
+
54
+ if typeName ~= typeof(endValue) then
55
+ return
56
+ end
57
+
58
+ local duration = rng:NextNumber(durationRange.Min, durationRange.Max)
59
+
60
+ local speedStart = attr.get(obj, "Speed_Start", 1)
61
+ local speedEnd = attr.get(obj, "Speed_End", 1)
62
+
63
+ local currentSpeed = speedStart
64
+
65
+ local speedTween
66
+
67
+ if speedStart ~= speedEnd then
68
+ speedTween = tween.fromParams(
69
+ attr.get(obj, "Speed_Curve", utility.default_bezier),
70
+ attr.get(obj, "Speed_Duration", 0.1),
71
+ function(alpha, deltaTime)
72
+ currentSpeed = utility.lerp(speedStart, speedEnd, alpha)
73
+ return deltaTime
74
+ end
75
+ )
76
+
77
+ table.insert(scope, speedTween)
78
+ end
79
+
80
+ local lfunc = lerp[typeName] or lerp.Other
81
+
82
+ local animator = if isAttribute
83
+ then function(alpha, deltaTime)
84
+ attr.set(target, name, lfunc(startValue, endValue, alpha))
85
+ return deltaTime * currentSpeed
86
+ end
87
+ else function(alpha, deltaTime)
88
+ target[name] = lfunc(startValue, endValue, alpha)
89
+ return deltaTime * currentSpeed
90
+ end
91
+
92
+ table.insert(
93
+ scope,
94
+ tween.fromParams(
95
+ attr.get(obj, "Easing_Curve", utility.linear_bezier),
96
+ duration,
97
+ animator,
98
+ speedTween,
99
+ nil,
100
+ nil,
101
+ utility.RENDER_PRIORITY + scope.depth
102
+ )
103
+ )
104
+
105
+ if resetOnFinish then
106
+ table.insert(scope, function()
107
+ if isAttribute then
108
+ attr.set(target, name, startValue)
109
+ else
110
+ target[name] = startValue
111
+ end
112
+ end)
113
+ end
114
+
115
+ tween.timer(duration, function(deltaTime, elapsed)
116
+ return if currentSpeed > 0 or (elapsed > 0 and speedTween and speedTween.Connected)
117
+ then deltaTime * currentSpeed
118
+ else nil
119
+ end, speedTween, scope)
120
+ end
121
+
122
+ return tweener
@@ -0,0 +1,387 @@
1
+ local TS = _G[script.Parent.Parent]
2
+ local Beam = require(script.Parent.effects.beam)
3
+ local Spin = require(script.Parent.effects.spin)
4
+ local Mesh = require(script.Parent.effects.mesh)
5
+ local Sound = require(script.Parent.effects.sound)
6
+ local Bezier = require(script.Parent.effects.bezier)
7
+ local Screen = require(script.Parent.effects.screen)
8
+ local Particle = require(script.Parent.effects.particle)
9
+ local Lightning = require(script.Parent.effects.lightning)
10
+ local CameraShake = require(script.Parent.effects.camera_shake)
11
+ local Tweener = require(script.Parent.effects.tweener)
12
+ local Randomizer = require(script.Parent.effects.randomizer)
13
+ local ShockwaveRing = require(script.Parent.effects.shockwave_ring)
14
+ local ShockwaveLine = require(script.Parent.effects.shockwave_line)
15
+ local ShockwaveDebris = require(script.Parent.effects.shockwave_debris)
16
+
17
+ local attr = require(script.Parent.mod.attributes)
18
+ local types = require(script.Parent.types)
19
+ local utility = require(script.Parent.mod.utility)
20
+
21
+
22
+ local emitters = {}
23
+
24
+ local function processRandomizers(obj: Instance, scope: types.scope)
25
+ local r_prop = obj:QueryDescendants(`> RayValue.{utility.PROPERTY_RANDOMIZER_TAG}`)
26
+ local r_attr = obj:QueryDescendants(`> RayValue.{utility.ATTRIBUTE_RANDOMIZER_TAG}`)
27
+
28
+ -- allow emitting the randomizer directly
29
+ if obj:IsA("RayValue") then
30
+ if obj:HasTag(utility.PROPERTY_RANDOMIZER_TAG) then
31
+ table.insert(r_prop, obj)
32
+ elseif obj:HasTag(utility.ATTRIBUTE_RANDOMIZER_TAG) then
33
+ table.insert(r_attr, obj)
34
+ end
35
+ end
36
+
37
+ for _, obj in r_prop do
38
+ utility.try(`failed to emit property randomizer with error: %s`, Randomizer.emit, obj, scope, false)
39
+ end
40
+
41
+ for _, obj in r_attr do
42
+ utility.try(`failed to emit attribute randomizer with error: %s`, Randomizer.emit, obj, scope, true)
43
+ end
44
+ end
45
+
46
+ function emitters.particle(
47
+ obj: ParticleEmitter,
48
+ scope: types.scope,
49
+ sharedUncMap: { [Instance]: number },
50
+ sharedRefMap: { [Instance]: Instance },
51
+ legacyScale: number
52
+ )
53
+ local count = attr.get(obj, "EmitCount", 1, true)
54
+ local duration = attr.get(obj, "EmitDuration", 0, true)
55
+
56
+ if count <= 0 and duration <= 0 then
57
+ return
58
+ end
59
+
60
+ if not obj:IsDescendantOf(workspace) then
61
+ local parent, ancestor = utility.cloneParticleAncestry(obj, sharedRefMap)
62
+
63
+ if not parent then
64
+ return
65
+ end
66
+
67
+ local clone = obj:Clone()
68
+ clone.Archivable = false
69
+ clone.Parent = parent
70
+
71
+ if not sharedUncMap[ancestor] then
72
+ ancestor.Parent = workspace.Terrain
73
+ sharedUncMap[ancestor] = 1
74
+ else
75
+ sharedUncMap[ancestor] += 1
76
+ end
77
+
78
+ table.insert(scope, function()
79
+ sharedUncMap[ancestor] -= 1
80
+
81
+ if sharedUncMap[ancestor] <= 0 then
82
+ ancestor:Destroy()
83
+ end
84
+ end)
85
+
86
+ utility.try(`failed to emit particle with error: %s`, Particle.emit, obj, clone, scope, legacyScale)
87
+ else
88
+ utility.try(`failed to emit particle with error: %s`, Particle.emit, obj, obj, scope, legacyScale)
89
+ end
90
+ end
91
+
92
+ function emitters.beam(obj: Beam, scope: types.scope, legacyScale: number)
93
+ local att0, att1 = obj.Attachment0, obj.Attachment1
94
+
95
+ if not att0 or not att1 then
96
+ return
97
+ end
98
+
99
+ local clone = obj:Clone()
100
+ clone.Archivable = false
101
+ clone.Parent = workspace.Terrain
102
+
103
+ -- clone attachments for beams with scaled length
104
+ local unscaled = NumberRange.new(1, 1)
105
+
106
+ if
107
+ attr.get(obj, "Length_Scale_Start", unscaled, true) ~= unscaled
108
+ or attr.get(obj, "Length_Scale_End", unscaled, true) ~= unscaled
109
+ then
110
+ local a, b = att0:Clone(), att1:Clone()
111
+
112
+ table.insert(scope, a)
113
+ table.insert(scope, b)
114
+
115
+ a.Name = "_ForgeTempAttachment"
116
+ b.Name = "_ForgeTempAttachment"
117
+
118
+ a:AddTag(utility.EMIT_EXCLUDE_TAG)
119
+ b:AddTag(utility.EMIT_EXCLUDE_TAG)
120
+
121
+ clone.Attachment0 = a
122
+ clone.Attachment1 = b
123
+
124
+ a.Parent = att0.Parent
125
+ b.Parent = att1.Parent
126
+ end
127
+
128
+ table.insert(scope, clone)
129
+
130
+ utility.try(`failed to emit beam with error: %s`, Beam.emit, obj, clone, scope, legacyScale)
131
+ end
132
+
133
+ function emitters.trail(obj: Trail)
134
+ obj.Enabled = true
135
+ end
136
+
137
+ function emitters.bezier(obj: Instance, scope: types.scope, skipEnabledCheck: boolean?)
138
+ local part = obj:FindFirstChildOfClass("Part")
139
+
140
+ if not part then
141
+ return
142
+ end
143
+
144
+ if not skipEnabledCheck and obj:GetAttribute("Enabled") then
145
+ obj:SetAttribute("Enabled", false)
146
+ end
147
+
148
+ local clone = part:Clone()
149
+ clone.Locked = true
150
+
151
+ table.insert(scope, clone)
152
+
153
+ utility.try(`failed to emit bezier with error: %s`, Bezier.emit, obj, clone, scope, skipEnabledCheck)
154
+ end
155
+
156
+ function emitters.lightning(obj: Instance, scope: types.scope, skipEnabledCheck: boolean?)
157
+ local part = obj:FindFirstChildOfClass("Part")
158
+
159
+ if not part then
160
+ return
161
+ end
162
+
163
+ if not skipEnabledCheck and obj:GetAttribute("Enabled") then
164
+ obj:SetAttribute("Enabled", false)
165
+ end
166
+
167
+ local clone = part:Clone()
168
+ clone.Locked = true
169
+
170
+ table.insert(scope, clone)
171
+
172
+ utility.try(`failed to emit lightning with error: %s`, Lightning.emit, obj, clone, scope, skipEnabledCheck)
173
+ end
174
+
175
+ function emitters.mesh(
176
+ obj: Instance,
177
+ scope: types.scope,
178
+ shared_part_cache: any,
179
+ legacyScale: number,
180
+ skipEnabledCheck: boolean?
181
+ )
182
+ local start = obj:FindFirstChild("Start")
183
+
184
+ if not start then
185
+ return
186
+ end
187
+
188
+ if not skipEnabledCheck and obj:GetAttribute("Enabled") then
189
+ obj:SetAttribute("Enabled", false)
190
+ end
191
+
192
+ local tasks = {}
193
+
194
+ for i = 1, attr.get(obj, "EmitCount", 1) do
195
+ table.insert(
196
+ tasks,
197
+ TS.Promise.new(function(resolve)
198
+ utility.try(
199
+ `failed to emit mesh with error: %s`,
200
+ Mesh.emit,
201
+ obj,
202
+ utility.assembleMeshVFX(start, scope, shared_part_cache),
203
+ scope,
204
+ legacyScale,
205
+ skipEnabledCheck
206
+ )
207
+
208
+ resolve()
209
+ end)
210
+ )
211
+ end
212
+
213
+ TS.Promise.all(tasks):await()
214
+ end
215
+
216
+ function emitters.spin(obj: Model, scope: types.scope)
217
+ if utility.lock(obj) then
218
+ return
219
+ end
220
+
221
+ utility.try(`failed to emit spinning model with error: %s`, Spin.emit, obj, scope)
222
+ utility.unlock(obj)
223
+ end
224
+
225
+ function emitters.camera_shake(obj: RayValue, scope: types.scope)
226
+ utility.try(`failed to emit camera shake with error: %s`, CameraShake.emit, obj, scope)
227
+ end
228
+
229
+ function emitters.property_tweener(obj: RayValue, scope: types.scope)
230
+ if not obj.Parent or utility.lock(obj) then
231
+ return
232
+ end
233
+
234
+ utility.try(`failed to emit property tweener with error: %s`, Tweener.emit, obj, scope, false)
235
+ utility.unlock(obj)
236
+ end
237
+
238
+ function emitters.attribute_tweener(obj: RayValue, scope: types.scope)
239
+ if not obj.Parent or utility.lock(obj) then
240
+ return
241
+ end
242
+
243
+ utility.try(`failed to emit attribute tweener with error: %s`, Tweener.emit, obj.Parent, obj, scope, true)
244
+ utility.unlock(obj)
245
+ end
246
+
247
+ function emitters.screen(obj: BasePart, scope: types.scope)
248
+ local ok, result = utility.try(`failed to emit screen effect with error: %s`, Screen.emit, obj, scope)
249
+
250
+ return if ok then result else false
251
+ end
252
+
253
+ function emitters.shockwave_ring(att: Attachment, obj: Part, scope: types.scope)
254
+ utility.try(`failed to emit shockwave ring with error: %s`, ShockwaveRing.emit, att, obj, scope)
255
+ end
256
+
257
+ function emitters.shockwave_debris(att: Attachment, obj: Part, scope: types.scope)
258
+ utility.try(`failed to emit shockwave debris with error: %s`, ShockwaveDebris.emit, att, obj, scope)
259
+ end
260
+
261
+ function emitters.shockwave_line(att: Attachment, obj: Part, scope: types.scope)
262
+ utility.try(`failed to emit shockwave line with error: %s`, ShockwaveLine.emit, att, obj, scope)
263
+ end
264
+
265
+ function emitters.sound(obj: Sound, scope: types.scope)
266
+ local emitCount = attr.get(obj, "EmitCount", 1, true)
267
+
268
+ if emitCount <= 0 then
269
+ return
270
+ end
271
+
272
+ utility.try(`failed to emit sound with error: %s`, Sound.emit, obj, scope)
273
+ end
274
+
275
+ -- Dispatch function to determine which emitter to use and call it
276
+ function emitters.dispatch(
277
+ obj: any, -- since we don't use IsA(...), we don't get type narrowing
278
+ scope: types.scope,
279
+ sharedUncMap: { [Instance]: number },
280
+ sharedRefMap: { [Instance]: Instance },
281
+ shared_part_cache: any,
282
+ legacyScale: number
283
+ )
284
+ local className = obj.ClassName
285
+
286
+ processRandomizers(obj, scope)
287
+
288
+ if className == "ParticleEmitter" then
289
+ emitters.particle(obj, scope, sharedUncMap, sharedRefMap, legacyScale)
290
+ return
291
+ elseif className == "Beam" then
292
+ emitters.beam(obj, scope, legacyScale)
293
+ return
294
+ elseif className == "Trail" then
295
+ emitters.trail(obj)
296
+ return
297
+ elseif className == "Sound" then
298
+ emitters.sound(obj, scope)
299
+ return
300
+ elseif className == "RayValue" then
301
+ if obj:HasTag(utility.SCREENSHAKE_TAG) then
302
+ emitters.camera_shake(obj, scope)
303
+ elseif obj:HasTag(utility.ATTRIBUTE_TWEENER_TAG) then
304
+ emitters.attribute_tweener(obj, scope)
305
+ elseif obj:HasTag(utility.PROPERTY_RANDOMIZER_TAG) or obj:HasTag(utility.ATTRIBUTE_RANDOMIZER_TAG) then
306
+ -- skip randomizer effects because they have already been emitted
307
+ else
308
+ -- fall back to property tweens for backwards compatibility
309
+ emitters.property_tweener(obj, scope)
310
+ end
311
+
312
+ return
313
+ end
314
+
315
+ if obj:HasTag(utility.BEZIER_TAG) then
316
+ emitters.bezier(obj, scope)
317
+ return
318
+ end
319
+
320
+ if obj:HasTag(utility.LIGHTNING_TAG) then
321
+ emitters.lightning(obj, scope)
322
+ return
323
+ end
324
+
325
+ if className == "Model" then
326
+ if utility.isMeshVFX(obj) then
327
+ emitters.mesh(obj, scope, shared_part_cache, legacyScale)
328
+ else
329
+ emitters.spin(obj, scope)
330
+ end
331
+
332
+ return
333
+ end
334
+
335
+ if className == "Part" then
336
+ local att = utility.findFirstClassWithTag(obj, "Attachment", utility.SHOCKWAVE_TAG)
337
+
338
+ if att then
339
+ local name = obj.Parent and obj.Parent.Name or ""
340
+
341
+ if name == "Rings" then
342
+ emitters.shockwave_ring(att, obj, scope)
343
+ elseif name == "Debris" then
344
+ emitters.shockwave_debris(att, obj, scope)
345
+ elseif name == "Lines" then
346
+ emitters.shockwave_line(att, obj, scope)
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ -- Registry for enabled effects (effects that emit continuously)
353
+ type EnabledEffectConfig = {
354
+ check: (obj: Instance) -> boolean,
355
+ emit: (obj: Instance, scope: types.scope, shared_part_cache: any) -> (),
356
+ }
357
+
358
+ emitters.enabled_registry = {
359
+ mesh = {
360
+ check = function(obj: Instance)
361
+ return utility.isMeshVFX(obj)
362
+ end,
363
+ emit = function(obj: Instance, scope: types.scope, shared_part_cache: any)
364
+ emitters.mesh(obj, scope, shared_part_cache, 1, true)
365
+ end,
366
+ },
367
+
368
+ bezier = {
369
+ check = function(obj: Instance)
370
+ return obj:HasTag(utility.BEZIER_TAG)
371
+ end,
372
+ emit = function(obj: Instance, scope: types.scope, shared_part_cache: any)
373
+ emitters.bezier(obj, scope, true)
374
+ end,
375
+ },
376
+
377
+ lightning = {
378
+ check = function(obj: Instance)
379
+ return obj:HasTag(utility.LIGHTNING_TAG)
380
+ end,
381
+ emit = function(obj: Instance, scope: types.scope, shared_part_cache: any)
382
+ emitters.lightning(obj, scope, true)
383
+ end,
384
+ },
385
+ } :: { [string]: EnabledEffectConfig }
386
+
387
+ return emitters