@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.
- package/LICENSE +82 -0
- package/README.md +39 -0
- package/out/forge-vfx/effects/beam.luau +312 -0
- package/out/forge-vfx/effects/bezier.luau +392 -0
- package/out/forge-vfx/effects/camera_shake.luau +200 -0
- package/out/forge-vfx/effects/lightning.luau +1183 -0
- package/out/forge-vfx/effects/mesh.luau +466 -0
- package/out/forge-vfx/effects/particle.luau +64 -0
- package/out/forge-vfx/effects/randomizer.luau +110 -0
- package/out/forge-vfx/effects/screen.luau +61 -0
- package/out/forge-vfx/effects/shockwave_debris.luau +277 -0
- package/out/forge-vfx/effects/shockwave_line.luau +356 -0
- package/out/forge-vfx/effects/shockwave_ring.luau +252 -0
- package/out/forge-vfx/effects/sound.luau +311 -0
- package/out/forge-vfx/effects/spin.luau +88 -0
- package/out/forge-vfx/effects/tweener.luau +122 -0
- package/out/forge-vfx/emitters.luau +387 -0
- package/out/forge-vfx/index.d.ts +356 -0
- package/out/forge-vfx/init.luau +279 -0
- package/out/forge-vfx/mod/attributes.luau +227 -0
- package/out/forge-vfx/mod/color/Oklab.luau +93 -0
- package/out/forge-vfx/mod/color/sRGB.luau +71 -0
- package/out/forge-vfx/mod/common/bezier.luau +372 -0
- package/out/forge-vfx/mod/common/flipbook.luau +102 -0
- package/out/forge-vfx/mod/lerp.luau +210 -0
- package/out/forge-vfx/mod/logger.luau +20 -0
- package/out/forge-vfx/mod/shape.luau +207 -0
- package/out/forge-vfx/mod/tween.luau +161 -0
- package/out/forge-vfx/mod/utility.luau +707 -0
- package/out/forge-vfx/obj/Bezier.luau +268 -0
- package/out/forge-vfx/obj/ObjectCache.luau +289 -0
- package/out/forge-vfx/services/caches.luau +62 -0
- package/out/forge-vfx/services/effects.luau +234 -0
- package/out/forge-vfx/services/enabled_effects.luau +120 -0
- package/out/forge-vfx/services/texture_loader.luau +174 -0
- package/out/forge-vfx/types.luau +43 -0
- package/out/index.d.ts +3 -0
- package/out/init.luau +5 -0
- package/out/shake.d.ts +2 -0
- package/out/shake.luau +6 -0
- package/out/tsconfig.tsbuildinfo +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
local TS = _G[script.Parent.Parent.Parent]
|
|
2
|
+
--!nolint LocalShadow
|
|
3
|
+
local attr = require(script.Parent.Parent.mod.attributes)
|
|
4
|
+
local tween = require(script.Parent.Parent.mod.tween)
|
|
5
|
+
local types = require(script.Parent.Parent.types)
|
|
6
|
+
local utility = require(script.Parent.Parent.mod.utility)
|
|
7
|
+
local bezier_common = require(script.Parent.Parent.mod.common.bezier)
|
|
8
|
+
|
|
9
|
+
local Bezier = require(script.Parent.Parent.obj.Bezier)
|
|
10
|
+
local ObjectCache = require(script.Parent.Parent.obj.ObjectCache)
|
|
11
|
+
|
|
12
|
+
local lightning = {}
|
|
13
|
+
|
|
14
|
+
local part_cache: ObjectCache.ObjectCache?
|
|
15
|
+
|
|
16
|
+
function lightning.init(cache)
|
|
17
|
+
part_cache = cache
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
function lightning.deinit()
|
|
21
|
+
part_cache = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
local function readLightningAttributes(ref: Attachment)
|
|
25
|
+
return {
|
|
26
|
+
-- lightning
|
|
27
|
+
segments = math.max(attr.get(ref, "Segments", 8), 2),
|
|
28
|
+
jaggedness = attr.getRange(ref, "Jaggedness", NumberRange.new(0.5, 1), NumberRange.new(0, math.huge)),
|
|
29
|
+
offsetScale = attr.get(ref, "OffsetScale", 1),
|
|
30
|
+
refreshRate = attr.get(ref, "RefreshRate", 15),
|
|
31
|
+
independentSegments = attr.get(ref, "IndependentSegments", false),
|
|
32
|
+
refreshDuringDissipate = attr.get(ref, "RefreshDuringDissipate", false),
|
|
33
|
+
nestedEffectMode = attr.getEnum(ref, "NestedEffectMode", "None", { "None", "All", "Head", "Tail" }),
|
|
34
|
+
|
|
35
|
+
-- color
|
|
36
|
+
colorSequence = attr.get(ref, "Color", ColorSequence.new(Color3.new(1, 1, 1))),
|
|
37
|
+
colorEasingData = attr.get(ref, "Color_Curve", utility.linear_bezier),
|
|
38
|
+
colorDuration = attr.get(ref, "Color_Duration", 1),
|
|
39
|
+
|
|
40
|
+
-- transparency
|
|
41
|
+
transparencyStart = attr.get(ref, "Transparency_Start", 0),
|
|
42
|
+
transparencyEnd = attr.get(ref, "Transparency_End", 0),
|
|
43
|
+
|
|
44
|
+
-- fill
|
|
45
|
+
fillColorSequence = attr.get(ref, "Fill_Color", ColorSequence.new(Color3.new(1, 1, 1))),
|
|
46
|
+
fillColorEasingData = attr.get(ref, "Fill_Color_Curve", utility.linear_bezier),
|
|
47
|
+
fillColorDuration = attr.get(ref, "Fill_Color_Duration", 1),
|
|
48
|
+
fillTransparencyStart = attr.get(ref, "Fill_Transparency_Start", 1),
|
|
49
|
+
fillTransparencyEnd = attr.get(ref, "Fill_Transparency_End", 1),
|
|
50
|
+
fillDepthMode = attr.getEnum(ref, "Fill_DepthMode", "Occluded", { "AlwaysOnTop", "Occluded" }),
|
|
51
|
+
|
|
52
|
+
-- fade-in
|
|
53
|
+
fadeInStart = attr.get(ref, "Fade_In_Start", 1),
|
|
54
|
+
fadeInDuration = attr.get(ref, "Fade_In_Duration", 0),
|
|
55
|
+
fadeInCurveData = attr.get(ref, "Fade_In_Curve", utility.default_bezier),
|
|
56
|
+
|
|
57
|
+
-- fade-out
|
|
58
|
+
fadeOutEnd = attr.get(ref, "Fade_Out_End", 1),
|
|
59
|
+
fadeOutDuration = attr.get(ref, "Fade_Out_Duration", 0),
|
|
60
|
+
fadeOutCurveData = attr.get(ref, "Fade_Out_Curve", utility.default_bezier),
|
|
61
|
+
|
|
62
|
+
-- length
|
|
63
|
+
lengthStart = attr.get(ref, "Length_Start", 1),
|
|
64
|
+
lengthEnd = attr.get(ref, "Length_End", 1),
|
|
65
|
+
|
|
66
|
+
-- dissipation
|
|
67
|
+
dissipateMode = attr.getEnum(ref, "Dissipate_Mode", "None", { "None", "Retract", "Scale" }),
|
|
68
|
+
dissipateDuration = attr.get(ref, "Dissipate_Duration", 0.5),
|
|
69
|
+
dissipateCurveData = attr.get(ref, "Dissipate_Curve", utility.default_bezier),
|
|
70
|
+
|
|
71
|
+
-- width
|
|
72
|
+
widthStart = attr.get(ref, "Width_Start", 2),
|
|
73
|
+
widthEnd = attr.get(ref, "Width_End", 0.2),
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
local function createSegmentStates(count: number)
|
|
78
|
+
local states = {}
|
|
79
|
+
|
|
80
|
+
for j = 1, count do
|
|
81
|
+
states[j] = {
|
|
82
|
+
birthTime = nil,
|
|
83
|
+
initialWidth = nil,
|
|
84
|
+
initialTransparency = nil,
|
|
85
|
+
wasVisible = false,
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
return states
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
local function computeJaggedOffset(
|
|
93
|
+
rng: Random,
|
|
94
|
+
jaggedness: NumberRange,
|
|
95
|
+
offsetScale: number,
|
|
96
|
+
segLength: number,
|
|
97
|
+
right: Vector3,
|
|
98
|
+
up: Vector3
|
|
99
|
+
)
|
|
100
|
+
local jaggedAmount = rng:NextNumber(jaggedness.Min, jaggedness.Max)
|
|
101
|
+
local maxOffset = segLength * 0.8
|
|
102
|
+
|
|
103
|
+
local offsetMag = math.min(jaggedAmount * offsetScale * segLength, maxOffset)
|
|
104
|
+
local angle = rng:NextNumber(0, math.pi * 2)
|
|
105
|
+
|
|
106
|
+
return (right * math.cos(angle) + up * math.sin(angle)) * offsetMag
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
local function generateLightningPoints(
|
|
110
|
+
rng: Random,
|
|
111
|
+
segments: number,
|
|
112
|
+
getPos: (number) -> Vector3,
|
|
113
|
+
jaggedness: NumberRange,
|
|
114
|
+
offsetScale: number
|
|
115
|
+
)
|
|
116
|
+
local points = table.create(segments + 1)
|
|
117
|
+
local invSegments = 1 / segments
|
|
118
|
+
|
|
119
|
+
points[1] = getPos(0)
|
|
120
|
+
|
|
121
|
+
local prevPos = points[1]
|
|
122
|
+
|
|
123
|
+
for j = 1, segments - 1 do
|
|
124
|
+
local t = j * invSegments
|
|
125
|
+
local pos = getPos(t)
|
|
126
|
+
|
|
127
|
+
local nextT = math.min(t + 0.01, 1)
|
|
128
|
+
local tangent = getPos(nextT) - pos
|
|
129
|
+
|
|
130
|
+
tangent = if tangent.Magnitude > 0.001 then tangent.Unit else Vector3.yAxis
|
|
131
|
+
|
|
132
|
+
local right, up = bezier_common.getPerpendicularVectors(tangent)
|
|
133
|
+
local segmentLength = (pos - prevPos).Magnitude
|
|
134
|
+
|
|
135
|
+
local offset = computeJaggedOffset(rng, jaggedness, offsetScale, segmentLength, right, up)
|
|
136
|
+
|
|
137
|
+
pos = pos + offset
|
|
138
|
+
prevPos = pos
|
|
139
|
+
|
|
140
|
+
points[j + 1] = pos
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
points[segments + 1] = getPos(1)
|
|
144
|
+
|
|
145
|
+
return points
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
local function generateProjectilePoints(
|
|
149
|
+
rng: Random,
|
|
150
|
+
segments: number,
|
|
151
|
+
tailPos: Vector3,
|
|
152
|
+
headPos: Vector3,
|
|
153
|
+
jaggedness: NumberRange,
|
|
154
|
+
offsetScale: number,
|
|
155
|
+
fallbackDir: Vector3
|
|
156
|
+
)
|
|
157
|
+
local points = table.create(segments + 1)
|
|
158
|
+
local invSegments = 1 / segments
|
|
159
|
+
|
|
160
|
+
local pathDir = headPos - tailPos
|
|
161
|
+
local pathLength = pathDir.Magnitude
|
|
162
|
+
|
|
163
|
+
local tangent = if pathLength > 0.001 then pathDir / pathLength else fallbackDir
|
|
164
|
+
|
|
165
|
+
local right, up = bezier_common.getPerpendicularVectors(tangent)
|
|
166
|
+
local segLength = pathLength * invSegments
|
|
167
|
+
|
|
168
|
+
points[1] = tailPos
|
|
169
|
+
|
|
170
|
+
for j = 1, segments - 1 do
|
|
171
|
+
local t = j * invSegments
|
|
172
|
+
|
|
173
|
+
local pos = tailPos:Lerp(headPos, t)
|
|
174
|
+
local offset = computeJaggedOffset(rng, jaggedness, offsetScale, segLength, right, up)
|
|
175
|
+
|
|
176
|
+
points[j + 1] = pos + offset
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
points[segments + 1] = headPos
|
|
180
|
+
|
|
181
|
+
return points
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
export type TransitionState = {
|
|
185
|
+
curvedTailT: number,
|
|
186
|
+
curvedLengthT: number,
|
|
187
|
+
boltLength: number,
|
|
188
|
+
transitionBuffer: number,
|
|
189
|
+
distanceTraveled: number,
|
|
190
|
+
jaggedOffsets: { Vector3 }?,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
local function getBlendedPosition(
|
|
194
|
+
t: number,
|
|
195
|
+
getPos: (number) -> Vector3,
|
|
196
|
+
straightTailPos: Vector3,
|
|
197
|
+
straightHeadPos: Vector3,
|
|
198
|
+
state: TransitionState
|
|
199
|
+
): Vector3
|
|
200
|
+
-- per-point transition alpha
|
|
201
|
+
local pointExitDistance = (1 - t) * state.boltLength
|
|
202
|
+
local pointOvershoot = state.distanceTraveled - pointExitDistance
|
|
203
|
+
local pointAlpha = math.clamp(pointOvershoot / state.transitionBuffer, 0, 1)
|
|
204
|
+
|
|
205
|
+
-- curved position on sliding bezier
|
|
206
|
+
local bezierProgress = math.min(state.distanceTraveled / state.boltLength, 1) * state.curvedLengthT
|
|
207
|
+
local bezierT = math.min(1, state.curvedTailT + t * state.curvedLengthT + bezierProgress)
|
|
208
|
+
|
|
209
|
+
local curvedPos = getPos(bezierT)
|
|
210
|
+
|
|
211
|
+
-- straight position
|
|
212
|
+
local straightPos = straightTailPos:Lerp(straightHeadPos, t)
|
|
213
|
+
|
|
214
|
+
return curvedPos:Lerp(straightPos, pointAlpha)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
local function generateTransitionPoints(
|
|
218
|
+
rng: Random,
|
|
219
|
+
segments: number,
|
|
220
|
+
getPos: (number) -> Vector3,
|
|
221
|
+
straightTailPos: Vector3,
|
|
222
|
+
straightHeadPos: Vector3,
|
|
223
|
+
jaggedness: NumberRange,
|
|
224
|
+
offsetScale: number,
|
|
225
|
+
fallbackDir: Vector3,
|
|
226
|
+
state: TransitionState
|
|
227
|
+
)
|
|
228
|
+
local points = table.create(segments + 1)
|
|
229
|
+
local offsets = table.create(segments + 1)
|
|
230
|
+
|
|
231
|
+
local invSegments = 1 / segments
|
|
232
|
+
|
|
233
|
+
local straightDir = straightHeadPos - straightTailPos
|
|
234
|
+
local straightLength = straightDir.Magnitude
|
|
235
|
+
|
|
236
|
+
local tangent = if straightLength > 0.001 then straightDir / straightLength else fallbackDir
|
|
237
|
+
local right, up = bezier_common.getPerpendicularVectors(tangent)
|
|
238
|
+
|
|
239
|
+
local segLength = straightLength * invSegments
|
|
240
|
+
|
|
241
|
+
for j = 0, segments do
|
|
242
|
+
local t = j * invSegments
|
|
243
|
+
local basePos = getBlendedPosition(t, getPos, straightTailPos, straightHeadPos, state)
|
|
244
|
+
|
|
245
|
+
if j == 0 or j == segments then
|
|
246
|
+
points[j + 1] = basePos
|
|
247
|
+
offsets[j + 1] = Vector3.zero
|
|
248
|
+
else
|
|
249
|
+
local offset = computeJaggedOffset(rng, jaggedness, offsetScale, segLength, right, up)
|
|
250
|
+
|
|
251
|
+
points[j + 1] = basePos + offset
|
|
252
|
+
offsets[j + 1] = offset
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
return points, offsets
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
local function updateTransitionPoints(
|
|
260
|
+
segments: number,
|
|
261
|
+
getPos: (number) -> Vector3,
|
|
262
|
+
straightTailPos: Vector3,
|
|
263
|
+
straightHeadPos: Vector3,
|
|
264
|
+
state: TransitionState
|
|
265
|
+
): { Vector3 }
|
|
266
|
+
local points = table.create(segments + 1)
|
|
267
|
+
|
|
268
|
+
local invSegments = 1 / segments
|
|
269
|
+
local offsets = state.jaggedOffsets
|
|
270
|
+
|
|
271
|
+
for j = 0, segments do
|
|
272
|
+
local t = j * invSegments
|
|
273
|
+
local basePos = getBlendedPosition(t, getPos, straightTailPos, straightHeadPos, state)
|
|
274
|
+
|
|
275
|
+
points[j + 1] = basePos + (if offsets then offsets[j + 1] else Vector3.zero)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
return points
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
local function isTransitionComplete(state: TransitionState): boolean
|
|
282
|
+
return state.distanceTraveled >= state.boltLength + state.transitionBuffer
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
function lightning.emit(ref: Attachment, refObj: Part, scope: types.scope, mustEmit: boolean?)
|
|
286
|
+
local root = ref:FindFirstChild("Points")
|
|
287
|
+
|
|
288
|
+
if not root or not root:IsA("Attachment") or not part_cache then
|
|
289
|
+
return
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
local common = bezier_common.readCommonAttributes(ref)
|
|
293
|
+
local attrs = readLightningAttributes(ref)
|
|
294
|
+
|
|
295
|
+
local drawFunc = bezier_common.drawFuncMap[common.shapeType]
|
|
296
|
+
and bezier_common.drawFuncMap[common.shapeType][common.shapeStyle]
|
|
297
|
+
|
|
298
|
+
if not drawFunc then
|
|
299
|
+
return
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
local useDuration = common.emitDuration > 0
|
|
303
|
+
|
|
304
|
+
task.wait(common.emitDelay)
|
|
305
|
+
|
|
306
|
+
if useDuration and not mustEmit then
|
|
307
|
+
attr.trigger(ref, "Enabled", true)
|
|
308
|
+
|
|
309
|
+
if common.speedStart ~= common.speedEnd then
|
|
310
|
+
attr.setState(ref, "SpeedTweening", true)
|
|
311
|
+
|
|
312
|
+
table.insert(
|
|
313
|
+
scope,
|
|
314
|
+
tween.fromParams(
|
|
315
|
+
attr.get(ref, "Speed_Curve", utility.default_bezier),
|
|
316
|
+
attr.get(ref, "Speed_Duration", 0.1),
|
|
317
|
+
function(alpha, deltaTime)
|
|
318
|
+
attr.setState(ref, "SpeedOverride", utility.lerp(common.speedStart, common.speedEnd, alpha))
|
|
319
|
+
return deltaTime
|
|
320
|
+
end,
|
|
321
|
+
nil,
|
|
322
|
+
function()
|
|
323
|
+
attr.setState(ref, "SpeedTweening", nil)
|
|
324
|
+
end
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
task.wait(common.emitDuration)
|
|
330
|
+
|
|
331
|
+
attr.trigger(ref, "Enabled", false)
|
|
332
|
+
attr.clearState(ref)
|
|
333
|
+
|
|
334
|
+
return
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
if common.emitCount <= 0 then
|
|
338
|
+
return
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
local parent = bezier_common.validateParent(ref)
|
|
342
|
+
|
|
343
|
+
if not parent then
|
|
344
|
+
return
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
local originCFrame, originSize = utility.getTransformedOriginExtents(parent)
|
|
348
|
+
|
|
349
|
+
if not originCFrame then
|
|
350
|
+
return
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
local endPoint, endT1 = bezier_common.findEndAttachments(ref)
|
|
354
|
+
local points = utility.getBezierPoints(root)
|
|
355
|
+
|
|
356
|
+
local rng = Random.new()
|
|
357
|
+
local baseBezier = not endPoint and Bezier.new(points)
|
|
358
|
+
|
|
359
|
+
local colorEasingBezier = Bezier.new(utility.deserializePath(attrs.colorEasingData), 0)
|
|
360
|
+
local fillColorEasingBezier = Bezier.new(utility.deserializePath(attrs.fillColorEasingData), 0)
|
|
361
|
+
|
|
362
|
+
local fadeInEasingBezier = Bezier.new(utility.deserializePath(attrs.fadeInCurveData), 0)
|
|
363
|
+
local fadeOutEasingBezier = Bezier.new(utility.deserializePath(attrs.fadeOutCurveData), 0)
|
|
364
|
+
|
|
365
|
+
local hitboxParams = bezier_common.createHitboxParams({
|
|
366
|
+
enabled = common.hitboxEnabled,
|
|
367
|
+
collisionGroup = common.hitboxCollisionGroup,
|
|
368
|
+
filterTag = common.hitboxFilterTag,
|
|
369
|
+
filterType = common.hitboxFilterType,
|
|
370
|
+
ignoreCanCollide = common.hitboxIgnoreCanCollide,
|
|
371
|
+
}, parent, root)
|
|
372
|
+
|
|
373
|
+
local promises = {}
|
|
374
|
+
|
|
375
|
+
for _ = 1, common.emitCount do
|
|
376
|
+
local duration = rng:NextNumber(common.duration.Min, common.duration.Max)
|
|
377
|
+
local lifetime = common.projectileEnabled
|
|
378
|
+
and rng:NextNumber(common.projectileLifetime.Min, common.projectileLifetime.Max)
|
|
379
|
+
|
|
380
|
+
table.insert(
|
|
381
|
+
promises,
|
|
382
|
+
TS.Promise.new(function(resolve)
|
|
383
|
+
local cf = bezier_common.calculateEmissionCFrame(originCFrame, originSize, {
|
|
384
|
+
face = common.face,
|
|
385
|
+
spreadAngle = common.spreadAngle,
|
|
386
|
+
mirror = common.mirror,
|
|
387
|
+
mirrorRot = common.mirrorRot,
|
|
388
|
+
partial = common.partial,
|
|
389
|
+
emissionDirection = common.emissionDirection,
|
|
390
|
+
}, drawFunc, rng, endPoint, parent:IsA("Attachment"))
|
|
391
|
+
|
|
392
|
+
local bezier = baseBezier or bezier_common.createBezierWithEndpoint(points, cf, endPoint, endT1)
|
|
393
|
+
local getPos = bezier_common.createPosGetter(bezier, points, cf, endPoint, true)
|
|
394
|
+
|
|
395
|
+
-- create segment parts
|
|
396
|
+
local segmentRealParts = table.create(attrs.segments)
|
|
397
|
+
local segmentCacheIds = table.create(attrs.segments)
|
|
398
|
+
|
|
399
|
+
local segmentState = createSegmentStates(attrs.segments)
|
|
400
|
+
|
|
401
|
+
local invSegments = 1 / attrs.segments
|
|
402
|
+
local segmentBounds = table.create(attrs.segments)
|
|
403
|
+
|
|
404
|
+
for j = 1, attrs.segments do
|
|
405
|
+
segmentBounds[j] = {
|
|
406
|
+
start = (j - 1) * invSegments,
|
|
407
|
+
finish = j * invSegments,
|
|
408
|
+
}
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
if not part_cache then
|
|
412
|
+
resolve()
|
|
413
|
+
return
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
-- create a highlight container if needed
|
|
417
|
+
local useHighlight = attrs.fillTransparencyStart < 1 or attrs.fillTransparencyEnd < 1
|
|
418
|
+
|
|
419
|
+
local highlight: Highlight?
|
|
420
|
+
local containerModel: Model?
|
|
421
|
+
|
|
422
|
+
if useHighlight then
|
|
423
|
+
local container = Instance.new("Model")
|
|
424
|
+
container.Name = "LightningContainer"
|
|
425
|
+
container.Parent = workspace.Terrain
|
|
426
|
+
containerModel = container
|
|
427
|
+
|
|
428
|
+
local hl = Instance.new("Highlight")
|
|
429
|
+
hl.Adornee = container
|
|
430
|
+
hl.FillColor = bezier_common.getColorWithEasingOklab(attrs.fillColorSequence, 0, fillColorEasingBezier)
|
|
431
|
+
hl.FillTransparency = attrs.fillTransparencyStart
|
|
432
|
+
hl.OutlineTransparency = 1
|
|
433
|
+
hl.DepthMode = Enum.HighlightDepthMode[attrs.fillDepthMode]
|
|
434
|
+
hl.Parent = container
|
|
435
|
+
|
|
436
|
+
highlight = hl
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
-- determine which segments get nested effects
|
|
440
|
+
local function shouldEmitNested(segmentIndex: number): boolean
|
|
441
|
+
local mode = attrs.nestedEffectMode
|
|
442
|
+
|
|
443
|
+
if mode == "All" then
|
|
444
|
+
return true
|
|
445
|
+
elseif mode == "Head" then
|
|
446
|
+
return segmentIndex == attrs.segments
|
|
447
|
+
elseif mode == "Tail" then
|
|
448
|
+
return segmentIndex == 1
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
return false
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
local emitOnFinish = if attrs.nestedEffectMode ~= "None"
|
|
455
|
+
then scope.effects.prepareEmitOnFinish(refObj, scope)
|
|
456
|
+
else nil
|
|
457
|
+
|
|
458
|
+
-- track segments that need nested effect emission
|
|
459
|
+
local nestedSegments = {}
|
|
460
|
+
local nestedEffectChildren = {}
|
|
461
|
+
|
|
462
|
+
-- create segment parts
|
|
463
|
+
for j = 1, attrs.segments do
|
|
464
|
+
local cacheId = utility.getRandomId()
|
|
465
|
+
|
|
466
|
+
local objAbstr = part_cache:get(cacheId)
|
|
467
|
+
local obj = objAbstr._getReal()
|
|
468
|
+
|
|
469
|
+
utility.copyProperties(refObj, obj, utility.COPY_PART_PROPERTIES)
|
|
470
|
+
|
|
471
|
+
obj.Size = Vector3.new(attrs.widthStart, attrs.widthStart, 1)
|
|
472
|
+
|
|
473
|
+
obj.Anchored = true
|
|
474
|
+
obj.CanQuery = false
|
|
475
|
+
obj.CanTouch = false
|
|
476
|
+
obj.CanCollide = false
|
|
477
|
+
|
|
478
|
+
if useHighlight and containerModel then
|
|
479
|
+
obj.Parent = containerModel
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
if shouldEmitNested(j) then
|
|
483
|
+
local clone = refObj:Clone()
|
|
484
|
+
|
|
485
|
+
for _, child in clone:GetChildren() do
|
|
486
|
+
child.Parent = obj
|
|
487
|
+
|
|
488
|
+
if attrs.nestedEffectMode == "Head" then
|
|
489
|
+
table.insert(nestedEffectChildren, child)
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
clone:Destroy()
|
|
494
|
+
|
|
495
|
+
table.insert(nestedSegments, j)
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
segmentRealParts[j] = obj
|
|
499
|
+
segmentCacheIds[j] = cacheId
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
-- for Head mode, reparent children to segment 1 initially
|
|
503
|
+
local currentNestedParentIndex
|
|
504
|
+
|
|
505
|
+
if attrs.nestedEffectMode == "Head" and #nestedEffectChildren > 0 then
|
|
506
|
+
for _, child in nestedEffectChildren do
|
|
507
|
+
child.Parent = segmentRealParts[1]
|
|
508
|
+
end
|
|
509
|
+
currentNestedParentIndex = 1
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
-- cleanup
|
|
513
|
+
table.insert(scope, function()
|
|
514
|
+
if part_cache then
|
|
515
|
+
for j = 1, attrs.segments do
|
|
516
|
+
part_cache:free(segmentCacheIds[j])
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
if containerModel then
|
|
520
|
+
containerModel:Destroy()
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
end)
|
|
524
|
+
|
|
525
|
+
local effectContainer = containerModel or refObj
|
|
526
|
+
|
|
527
|
+
-- animation state
|
|
528
|
+
local finished = false
|
|
529
|
+
|
|
530
|
+
local currentSpeed = common.speedStart
|
|
531
|
+
local currentWidth = attrs.widthStart
|
|
532
|
+
local currentLength = attrs.lengthStart
|
|
533
|
+
local currentTransparency = attrs.transparencyStart
|
|
534
|
+
local currentFillTransparency = attrs.fillTransparencyStart
|
|
535
|
+
|
|
536
|
+
local currentColorAlpha = 0
|
|
537
|
+
local currentFillColorAlpha = 0
|
|
538
|
+
|
|
539
|
+
local lastRefreshTime = 0
|
|
540
|
+
|
|
541
|
+
local lightningPoints =
|
|
542
|
+
generateLightningPoints(rng, attrs.segments, getPos, attrs.jaggedness, attrs.offsetScale)
|
|
543
|
+
|
|
544
|
+
-- emit nested effects
|
|
545
|
+
for _, j in nestedSegments do
|
|
546
|
+
local emitFrom = if attrs.nestedEffectMode == "Head" then 1 else j
|
|
547
|
+
local env = scope.effects.emitNested(segmentRealParts[emitFrom], scope.depth + 1)
|
|
548
|
+
table.insert(promises, env.Finished)
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
local lastVelocity = Vector3.zero
|
|
552
|
+
local lastHeadPos = Vector3.zero
|
|
553
|
+
|
|
554
|
+
local scaledElapsedTime = 0
|
|
555
|
+
|
|
556
|
+
local isDissipating = false
|
|
557
|
+
|
|
558
|
+
local currentHeadAlpha = 0
|
|
559
|
+
|
|
560
|
+
local dissipateProgress = 0
|
|
561
|
+
local dissipateStartTail = 0
|
|
562
|
+
|
|
563
|
+
local dissipateStartWidth = attrs.widthStart
|
|
564
|
+
local currentOriginCFrame = originCFrame
|
|
565
|
+
|
|
566
|
+
local projectileDir: Vector3?
|
|
567
|
+
local projectileHeadPos: Vector3?
|
|
568
|
+
local projectileTailPos: Vector3?
|
|
569
|
+
|
|
570
|
+
local transitionState: TransitionState?
|
|
571
|
+
|
|
572
|
+
local speedTween
|
|
573
|
+
|
|
574
|
+
if common.speedStart ~= common.speedEnd and not attr.getState(ref, "SpeedOverride", nil) then
|
|
575
|
+
speedTween = tween.fromParams(
|
|
576
|
+
attr.get(ref, "Speed_Curve", utility.default_bezier),
|
|
577
|
+
attr.get(ref, "Speed_Duration", 0.1),
|
|
578
|
+
function(alpha, deltaTime)
|
|
579
|
+
currentSpeed = utility.lerp(common.speedStart, common.speedEnd, alpha)
|
|
580
|
+
return deltaTime
|
|
581
|
+
end
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
table.insert(scope, speedTween)
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
local function getEffectiveSpeed()
|
|
588
|
+
return attr.getState(ref, "SpeedOverride", currentSpeed)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
local function isSpeedTweening()
|
|
592
|
+
return if speedTween then speedTween.Connected else attr.getState(ref, "SpeedTweening", false)
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
local function getSpeedDelta(deltaTime: number, allowZeroSpeed: boolean?)
|
|
596
|
+
local speed = getEffectiveSpeed()
|
|
597
|
+
currentSpeed = speed
|
|
598
|
+
|
|
599
|
+
if speed > 0 then
|
|
600
|
+
return deltaTime * speed
|
|
601
|
+
elseif allowZeroSpeed or isSpeedTweening() then
|
|
602
|
+
return deltaTime * speed
|
|
603
|
+
else
|
|
604
|
+
return nil
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
local function getHeadSegmentIndex(): number
|
|
609
|
+
return math.clamp(math.ceil(currentHeadAlpha * attrs.segments), 1, attrs.segments)
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
local function shapecast()
|
|
613
|
+
if not common.hitboxEnabled then
|
|
614
|
+
return false
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
local headSegment = segmentRealParts[getHeadSegmentIndex()]
|
|
618
|
+
|
|
619
|
+
if headSegment and headSegment.Transparency < 1 then
|
|
620
|
+
local result = workspace:GetPartsInPart(headSegment, hitboxParams)
|
|
621
|
+
|
|
622
|
+
if result[1] then
|
|
623
|
+
return true
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
return false
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
-- refresh lightning points based on current state
|
|
631
|
+
local function refreshPoints()
|
|
632
|
+
if projectileHeadPos and projectileTailPos and projectileDir then
|
|
633
|
+
if transitionState and not isTransitionComplete(transitionState) then
|
|
634
|
+
lightningPoints, transitionState.jaggedOffsets = generateTransitionPoints(
|
|
635
|
+
rng,
|
|
636
|
+
attrs.segments,
|
|
637
|
+
getPos,
|
|
638
|
+
projectileTailPos,
|
|
639
|
+
projectileHeadPos,
|
|
640
|
+
attrs.jaggedness,
|
|
641
|
+
attrs.offsetScale,
|
|
642
|
+
projectileDir,
|
|
643
|
+
transitionState
|
|
644
|
+
)
|
|
645
|
+
else
|
|
646
|
+
lightningPoints = generateProjectilePoints(
|
|
647
|
+
rng,
|
|
648
|
+
attrs.segments,
|
|
649
|
+
projectileTailPos,
|
|
650
|
+
projectileHeadPos,
|
|
651
|
+
attrs.jaggedness,
|
|
652
|
+
attrs.offsetScale,
|
|
653
|
+
projectileDir
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
if transitionState then
|
|
657
|
+
transitionState.jaggedOffsets = nil
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
else
|
|
661
|
+
lightningPoints = generateLightningPoints(rng, attrs.segments, getPos, attrs.jaggedness, attrs.offsetScale)
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
-- update points between refreshes during transition
|
|
666
|
+
local function updatePointsBetweenRefreshes(deltaTime: number)
|
|
667
|
+
if transitionState and not isTransitionComplete(transitionState) then
|
|
668
|
+
if transitionState.jaggedOffsets then
|
|
669
|
+
lightningPoints = updateTransitionPoints(
|
|
670
|
+
attrs.segments,
|
|
671
|
+
getPos,
|
|
672
|
+
projectileTailPos :: Vector3,
|
|
673
|
+
projectileHeadPos :: Vector3,
|
|
674
|
+
transitionState
|
|
675
|
+
)
|
|
676
|
+
else
|
|
677
|
+
-- generate offsets on first frame
|
|
678
|
+
lightningPoints, transitionState.jaggedOffsets = generateTransitionPoints(
|
|
679
|
+
rng,
|
|
680
|
+
attrs.segments,
|
|
681
|
+
getPos,
|
|
682
|
+
projectileTailPos :: Vector3,
|
|
683
|
+
projectileHeadPos :: Vector3,
|
|
684
|
+
attrs.jaggedness,
|
|
685
|
+
attrs.offsetScale,
|
|
686
|
+
projectileDir :: Vector3,
|
|
687
|
+
transitionState
|
|
688
|
+
)
|
|
689
|
+
end
|
|
690
|
+
else
|
|
691
|
+
-- fully transitioned, move points along the projectile direction
|
|
692
|
+
local offset = projectileDir * common.projectileSpeed * deltaTime
|
|
693
|
+
|
|
694
|
+
for j = 1, #lightningPoints do
|
|
695
|
+
lightningPoints[j] = lightningPoints[j] + offset
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
local function updateSegments(
|
|
701
|
+
headAlpha: number,
|
|
702
|
+
tailAlphaOverride: number?,
|
|
703
|
+
scaledDelta: number,
|
|
704
|
+
allowRefresh: boolean?,
|
|
705
|
+
widthOverride: number?
|
|
706
|
+
)
|
|
707
|
+
local currentTime = os.clock()
|
|
708
|
+
|
|
709
|
+
currentHeadAlpha = headAlpha
|
|
710
|
+
|
|
711
|
+
local tailAlpha = tailAlphaOverride or math.max(0, headAlpha - currentLength)
|
|
712
|
+
local effectiveWidth = widthOverride or currentWidth
|
|
713
|
+
|
|
714
|
+
-- refresh lightning points
|
|
715
|
+
if allowRefresh ~= false and attrs.refreshRate > 0 then
|
|
716
|
+
local refreshInterval = 1 / attrs.refreshRate
|
|
717
|
+
lastRefreshTime += scaledDelta
|
|
718
|
+
|
|
719
|
+
if lastRefreshTime >= refreshInterval then
|
|
720
|
+
refreshPoints()
|
|
721
|
+
lastRefreshTime = 0
|
|
722
|
+
end
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
-- update each segment
|
|
726
|
+
for j = 1, attrs.segments do
|
|
727
|
+
local state = segmentState[j]
|
|
728
|
+
|
|
729
|
+
local bounds = segmentBounds[j]
|
|
730
|
+
local segment = segmentRealParts[j]
|
|
731
|
+
|
|
732
|
+
local segStart = bounds.start
|
|
733
|
+
local segEnd = bounds.finish
|
|
734
|
+
|
|
735
|
+
local isVisible = segEnd > tailAlpha and segStart < headAlpha
|
|
736
|
+
|
|
737
|
+
if not isVisible then
|
|
738
|
+
segment.Transparency = 1
|
|
739
|
+
|
|
740
|
+
if state.wasVisible then
|
|
741
|
+
state.wasVisible = false
|
|
742
|
+
state.birthTime = nil
|
|
743
|
+
state.initialWidth = nil
|
|
744
|
+
state.initialTransparency = nil
|
|
745
|
+
end
|
|
746
|
+
else
|
|
747
|
+
if not state.wasVisible then
|
|
748
|
+
state.wasVisible = true
|
|
749
|
+
state.birthTime = currentTime
|
|
750
|
+
state.initialWidth = currentWidth
|
|
751
|
+
state.initialTransparency = currentTransparency
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
-- color
|
|
755
|
+
segment.Color =
|
|
756
|
+
bezier_common.getColorWithEasingOklab(attrs.colorSequence, currentColorAlpha, colorEasingBezier)
|
|
757
|
+
|
|
758
|
+
-- transparency with fade in/out
|
|
759
|
+
local baseTransparency = if attrs.independentSegments and state.initialTransparency
|
|
760
|
+
then state.initialTransparency
|
|
761
|
+
else currentTransparency
|
|
762
|
+
|
|
763
|
+
local fadeInAlpha = if attrs.fadeInDuration > 0
|
|
764
|
+
then math.clamp(scaledElapsedTime / attrs.fadeInDuration, 0, 1)
|
|
765
|
+
else 1
|
|
766
|
+
|
|
767
|
+
local fadeInEased = 1 - fadeInEasingBezier:getEase(fadeInAlpha).y
|
|
768
|
+
local fadeInTransparency = utility.lerp(attrs.fadeInStart, baseTransparency, fadeInEased)
|
|
769
|
+
|
|
770
|
+
local fadeOutTransparency = baseTransparency
|
|
771
|
+
|
|
772
|
+
if isDissipating and attrs.fadeOutDuration > 0 then
|
|
773
|
+
local fadeOutAlpha = math.clamp(dissipateProgress / attrs.fadeOutDuration, 0, 1)
|
|
774
|
+
local fadeOutEased = 1 - fadeOutEasingBezier:getEase(fadeOutAlpha).y
|
|
775
|
+
|
|
776
|
+
fadeOutTransparency = utility.lerp(baseTransparency, attrs.fadeOutEnd, fadeOutEased)
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
segment.Transparency = if fadeInAlpha < 1
|
|
780
|
+
then fadeInTransparency
|
|
781
|
+
elseif isDissipating then fadeOutTransparency
|
|
782
|
+
else baseTransparency
|
|
783
|
+
|
|
784
|
+
-- width
|
|
785
|
+
local useWidth
|
|
786
|
+
|
|
787
|
+
if widthOverride then
|
|
788
|
+
local baseWidth = if attrs.independentSegments and state.initialWidth
|
|
789
|
+
then state.initialWidth
|
|
790
|
+
else currentWidth
|
|
791
|
+
|
|
792
|
+
useWidth = baseWidth * (if currentWidth > 0 then widthOverride / currentWidth else 0)
|
|
793
|
+
elseif attrs.independentSegments and state.initialWidth then
|
|
794
|
+
useWidth = state.initialWidth
|
|
795
|
+
else
|
|
796
|
+
useWidth = effectiveWidth
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
-- geometry
|
|
800
|
+
local p1 = lightningPoints[j]
|
|
801
|
+
local p2 = lightningPoints[j + 1]
|
|
802
|
+
|
|
803
|
+
if p1 and p2 then
|
|
804
|
+
local clampedP1 = if segStart < tailAlpha
|
|
805
|
+
then p1:Lerp(p2, (tailAlpha - segStart) / (segEnd - segStart))
|
|
806
|
+
else p1
|
|
807
|
+
|
|
808
|
+
local clampedP2 = if segEnd > headAlpha
|
|
809
|
+
then p1:Lerp(p2, (headAlpha - segStart) / (segEnd - segStart))
|
|
810
|
+
else p2
|
|
811
|
+
|
|
812
|
+
local midpoint = (clampedP1 + clampedP2) / 2
|
|
813
|
+
local length = (clampedP2 - clampedP1).Magnitude
|
|
814
|
+
|
|
815
|
+
if length > 0.001 then
|
|
816
|
+
segment.Size = Vector3.new(useWidth, useWidth, length)
|
|
817
|
+
segment.CFrame = CFrame.lookAt(midpoint, clampedP2)
|
|
818
|
+
end
|
|
819
|
+
end
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
-- update fill
|
|
824
|
+
if highlight then
|
|
825
|
+
highlight.FillColor = bezier_common.getColorWithEasingOklab(
|
|
826
|
+
attrs.fillColorSequence,
|
|
827
|
+
currentFillColorAlpha,
|
|
828
|
+
fillColorEasingBezier
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
highlight.FillTransparency = currentFillTransparency
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
-- reparent nested effect children to current head segment (Head mode)
|
|
835
|
+
if currentNestedParentIndex then
|
|
836
|
+
local headIndex = getHeadSegmentIndex()
|
|
837
|
+
|
|
838
|
+
if headIndex ~= currentNestedParentIndex then
|
|
839
|
+
local targetSegment = segmentRealParts[headIndex]
|
|
840
|
+
|
|
841
|
+
for _, child in nestedEffectChildren do
|
|
842
|
+
child.Parent = targetSegment
|
|
843
|
+
end
|
|
844
|
+
|
|
845
|
+
currentNestedParentIndex = headIndex
|
|
846
|
+
end
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
-- track velocity
|
|
850
|
+
local currentHeadPos = getPos(math.min(headAlpha, 1))
|
|
851
|
+
|
|
852
|
+
if scaledDelta > 0 then
|
|
853
|
+
lastVelocity = (currentHeadPos - lastHeadPos) / scaledDelta
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
lastHeadPos = currentHeadPos
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
-- property tweens
|
|
860
|
+
bezier_common.createPropertyTween(scope, ref, "Width", duration, attrs.widthStart, attrs.widthEnd, function(v)
|
|
861
|
+
currentWidth = v
|
|
862
|
+
end, getEffectiveSpeed, speedTween)
|
|
863
|
+
|
|
864
|
+
bezier_common.createPropertyTween(
|
|
865
|
+
scope,
|
|
866
|
+
ref,
|
|
867
|
+
"Transparency",
|
|
868
|
+
duration,
|
|
869
|
+
attrs.transparencyStart,
|
|
870
|
+
attrs.transparencyEnd,
|
|
871
|
+
function(v)
|
|
872
|
+
currentTransparency = v
|
|
873
|
+
end,
|
|
874
|
+
getEffectiveSpeed,
|
|
875
|
+
speedTween
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
bezier_common.createPropertyTween(
|
|
879
|
+
scope,
|
|
880
|
+
ref,
|
|
881
|
+
"Length",
|
|
882
|
+
duration,
|
|
883
|
+
attrs.lengthStart,
|
|
884
|
+
attrs.lengthEnd,
|
|
885
|
+
function(v)
|
|
886
|
+
currentLength = v
|
|
887
|
+
end,
|
|
888
|
+
getEffectiveSpeed,
|
|
889
|
+
speedTween
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
if useHighlight then
|
|
893
|
+
bezier_common.createPropertyTween(
|
|
894
|
+
scope,
|
|
895
|
+
ref,
|
|
896
|
+
"FillTransparency",
|
|
897
|
+
attrs.fillColorDuration,
|
|
898
|
+
attrs.fillTransparencyStart,
|
|
899
|
+
attrs.fillTransparencyEnd,
|
|
900
|
+
function(v)
|
|
901
|
+
currentFillTransparency = v
|
|
902
|
+
end,
|
|
903
|
+
getEffectiveSpeed,
|
|
904
|
+
speedTween
|
|
905
|
+
)
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
-- dissipation handler
|
|
909
|
+
local function handleDissipation(
|
|
910
|
+
headAlpha: number,
|
|
911
|
+
startTail: number,
|
|
912
|
+
startWidth: number,
|
|
913
|
+
curveData: string,
|
|
914
|
+
dissipationDuration: number,
|
|
915
|
+
mode: string,
|
|
916
|
+
onComplete: () -> ()
|
|
917
|
+
)
|
|
918
|
+
isDissipating = true
|
|
919
|
+
dissipateStartTail = startTail
|
|
920
|
+
dissipateStartWidth = startWidth
|
|
921
|
+
|
|
922
|
+
tween.fromParams(curveData, dissipationDuration, function(alpha, deltaTime, elapsed)
|
|
923
|
+
dissipateProgress = elapsed
|
|
924
|
+
|
|
925
|
+
local scaledDelta = deltaTime * getEffectiveSpeed()
|
|
926
|
+
scaledElapsedTime += scaledDelta
|
|
927
|
+
|
|
928
|
+
if attrs.refreshDuringDissipate then
|
|
929
|
+
currentColorAlpha = (currentColorAlpha + scaledDelta / attrs.colorDuration) % 1
|
|
930
|
+
currentFillColorAlpha = (currentFillColorAlpha + scaledDelta / attrs.fillColorDuration) % 1
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
local tailOverride = if mode == "Retract" then utility.lerp(dissipateStartTail, headAlpha, alpha) else nil
|
|
934
|
+
local widthOverrideVal = if mode == "Scale" then utility.lerp(dissipateStartWidth, 0, alpha) else nil
|
|
935
|
+
|
|
936
|
+
updateSegments(headAlpha, tailOverride, scaledDelta, attrs.refreshDuringDissipate, widthOverrideVal)
|
|
937
|
+
|
|
938
|
+
return getSpeedDelta(deltaTime, true)
|
|
939
|
+
end, speedTween, onComplete, true, utility.RENDER_PRIORITY + scope.depth)
|
|
940
|
+
end
|
|
941
|
+
|
|
942
|
+
local function startDissipation()
|
|
943
|
+
if isDissipating or finished then
|
|
944
|
+
return
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
local headAlpha = currentHeadAlpha
|
|
948
|
+
|
|
949
|
+
local finishEnv = if emitOnFinish
|
|
950
|
+
then scope.effects.emitOnFinish(emitOnFinish, effectContainer, scope.depth + 1)
|
|
951
|
+
else nil
|
|
952
|
+
|
|
953
|
+
local function onDissipationComplete()
|
|
954
|
+
if finished then
|
|
955
|
+
return
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
finished = true
|
|
959
|
+
|
|
960
|
+
if finishEnv then
|
|
961
|
+
finishEnv.Finished:finally(function()
|
|
962
|
+
resolve()
|
|
963
|
+
end)
|
|
964
|
+
else
|
|
965
|
+
resolve()
|
|
966
|
+
end
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
if attrs.dissipateMode ~= "None" then
|
|
970
|
+
handleDissipation(
|
|
971
|
+
headAlpha,
|
|
972
|
+
math.max(0, headAlpha - currentLength),
|
|
973
|
+
currentWidth,
|
|
974
|
+
attrs.dissipateCurveData,
|
|
975
|
+
attrs.dissipateDuration,
|
|
976
|
+
attrs.dissipateMode,
|
|
977
|
+
onDissipationComplete
|
|
978
|
+
)
|
|
979
|
+
elseif attrs.fadeOutDuration > 0 then
|
|
980
|
+
handleDissipation(
|
|
981
|
+
headAlpha,
|
|
982
|
+
math.max(0, headAlpha - currentLength),
|
|
983
|
+
currentWidth,
|
|
984
|
+
attrs.fadeOutCurveData,
|
|
985
|
+
attrs.fadeOutDuration,
|
|
986
|
+
"None",
|
|
987
|
+
onDissipationComplete
|
|
988
|
+
)
|
|
989
|
+
else
|
|
990
|
+
onDissipationComplete()
|
|
991
|
+
end
|
|
992
|
+
end
|
|
993
|
+
|
|
994
|
+
-- main animation
|
|
995
|
+
table.insert(
|
|
996
|
+
scope,
|
|
997
|
+
tween.fromParams(
|
|
998
|
+
attr.get(ref, "Easing_Curve", utility.linear_bezier),
|
|
999
|
+
duration,
|
|
1000
|
+
function(alpha, deltaTime, elapsed)
|
|
1001
|
+
local scaledDelta = deltaTime * getEffectiveSpeed()
|
|
1002
|
+
scaledElapsedTime += scaledDelta
|
|
1003
|
+
|
|
1004
|
+
currentColorAlpha = (currentColorAlpha + scaledDelta / attrs.colorDuration) % 1
|
|
1005
|
+
currentFillColorAlpha = (currentFillColorAlpha + scaledDelta / attrs.fillColorDuration) % 1
|
|
1006
|
+
|
|
1007
|
+
if common.syncPosition then
|
|
1008
|
+
local newOrigin = utility.getTransformedOriginExtents(parent)
|
|
1009
|
+
local offset = newOrigin * currentOriginCFrame:Inverse()
|
|
1010
|
+
|
|
1011
|
+
for j = 1, #lightningPoints do
|
|
1012
|
+
lightningPoints[j] =
|
|
1013
|
+
offset:PointToWorldSpace(currentOriginCFrame:PointToObjectSpace(lightningPoints[j]))
|
|
1014
|
+
end
|
|
1015
|
+
|
|
1016
|
+
currentOriginCFrame = newOrigin
|
|
1017
|
+
end
|
|
1018
|
+
|
|
1019
|
+
updateSegments(alpha, nil, scaledDelta)
|
|
1020
|
+
|
|
1021
|
+
if shapecast() then
|
|
1022
|
+
startDissipation()
|
|
1023
|
+
return nil
|
|
1024
|
+
end
|
|
1025
|
+
|
|
1026
|
+
local speedDelta = getSpeedDelta(deltaTime)
|
|
1027
|
+
|
|
1028
|
+
if speedDelta == nil then
|
|
1029
|
+
return nil
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
if (common.projectileEnabled and alpha * duration < lifetime) or not common.projectileEnabled then
|
|
1033
|
+
return speedDelta
|
|
1034
|
+
else
|
|
1035
|
+
startDissipation()
|
|
1036
|
+
return nil
|
|
1037
|
+
end
|
|
1038
|
+
end,
|
|
1039
|
+
speedTween,
|
|
1040
|
+
function()
|
|
1041
|
+
if isDissipating or finished then
|
|
1042
|
+
return
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
-- non-projectile mode
|
|
1046
|
+
if not common.projectileEnabled then
|
|
1047
|
+
if common.destroyDelay > 0 then
|
|
1048
|
+
tween.fromParams(
|
|
1049
|
+
utility.linear_bezier,
|
|
1050
|
+
common.destroyDelay,
|
|
1051
|
+
function(_, deltaTime)
|
|
1052
|
+
local scaledDelta = deltaTime * getEffectiveSpeed()
|
|
1053
|
+
scaledElapsedTime += scaledDelta
|
|
1054
|
+
|
|
1055
|
+
currentColorAlpha = (currentColorAlpha + scaledDelta / attrs.colorDuration) % 1
|
|
1056
|
+
currentFillColorAlpha = (currentFillColorAlpha + scaledDelta / attrs.fillColorDuration) % 1
|
|
1057
|
+
|
|
1058
|
+
updateSegments(1, nil, scaledDelta, true)
|
|
1059
|
+
|
|
1060
|
+
if shapecast() then
|
|
1061
|
+
startDissipation()
|
|
1062
|
+
return nil
|
|
1063
|
+
end
|
|
1064
|
+
|
|
1065
|
+
return getSpeedDelta(deltaTime, true)
|
|
1066
|
+
end,
|
|
1067
|
+
speedTween,
|
|
1068
|
+
function()
|
|
1069
|
+
startDissipation()
|
|
1070
|
+
end,
|
|
1071
|
+
true,
|
|
1072
|
+
utility.RENDER_PRIORITY + scope.depth
|
|
1073
|
+
)
|
|
1074
|
+
else
|
|
1075
|
+
startDissipation()
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
return
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
-- projectile mode
|
|
1082
|
+
local dir = if common.projectileMatchEnd and endPoint
|
|
1083
|
+
then endPoint.WorldCFrame.LookVector
|
|
1084
|
+
else lastVelocity.Unit
|
|
1085
|
+
|
|
1086
|
+
if dir ~= dir or dir.Magnitude < 0.001 then
|
|
1087
|
+
dir = Vector3.zAxis
|
|
1088
|
+
else
|
|
1089
|
+
dir = dir.Unit
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
local initialHeadPos = getPos(1)
|
|
1093
|
+
local initialTailPos = getPos(math.max(0, 1 - currentLength))
|
|
1094
|
+
|
|
1095
|
+
local boltLength = (initialHeadPos - initialTailPos).Magnitude
|
|
1096
|
+
|
|
1097
|
+
local curvedTailT = math.max(0, 1 - currentLength)
|
|
1098
|
+
local curvedLengthT = 1 - curvedTailT
|
|
1099
|
+
|
|
1100
|
+
local transitionBuffer = boltLength * 0.3
|
|
1101
|
+
|
|
1102
|
+
-- preserve existing jaggedness offsets from bezier phase
|
|
1103
|
+
local existingOffsets = table.create(attrs.segments + 1)
|
|
1104
|
+
|
|
1105
|
+
for j = 0, attrs.segments do
|
|
1106
|
+
local t = j / attrs.segments
|
|
1107
|
+
local bezierT = curvedTailT + t * curvedLengthT
|
|
1108
|
+
local basePos = getPos(bezierT)
|
|
1109
|
+
|
|
1110
|
+
existingOffsets[j + 1] = lightningPoints[j + 1] - basePos
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
local state: TransitionState = {
|
|
1114
|
+
curvedTailT = curvedTailT,
|
|
1115
|
+
curvedLengthT = curvedLengthT,
|
|
1116
|
+
boltLength = boltLength,
|
|
1117
|
+
transitionBuffer = transitionBuffer,
|
|
1118
|
+
distanceTraveled = 0,
|
|
1119
|
+
jaggedOffsets = existingOffsets,
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
transitionState = state
|
|
1123
|
+
|
|
1124
|
+
tween.timer(lifetime, function(deltaTime, elapsed)
|
|
1125
|
+
local scaledDelta = deltaTime * getEffectiveSpeed()
|
|
1126
|
+
scaledElapsedTime += scaledDelta
|
|
1127
|
+
|
|
1128
|
+
currentColorAlpha = (currentColorAlpha + scaledDelta / attrs.colorDuration) % 1
|
|
1129
|
+
currentFillColorAlpha = (currentFillColorAlpha + scaledDelta / attrs.fillColorDuration) % 1
|
|
1130
|
+
|
|
1131
|
+
local distanceTraveled = elapsed * common.projectileSpeed
|
|
1132
|
+
state.distanceTraveled = distanceTraveled
|
|
1133
|
+
|
|
1134
|
+
local currentHeadPos = initialHeadPos + dir * distanceTraveled
|
|
1135
|
+
local currentTailPos = currentHeadPos - dir * boltLength
|
|
1136
|
+
|
|
1137
|
+
projectileHeadPos = currentHeadPos
|
|
1138
|
+
projectileTailPos = currentTailPos
|
|
1139
|
+
projectileDir = dir
|
|
1140
|
+
|
|
1141
|
+
local shouldRefresh = attrs.refreshRate > 0
|
|
1142
|
+
|
|
1143
|
+
if shouldRefresh then
|
|
1144
|
+
lastRefreshTime += scaledDelta
|
|
1145
|
+
|
|
1146
|
+
if lastRefreshTime >= 1 / attrs.refreshRate then
|
|
1147
|
+
refreshPoints()
|
|
1148
|
+
lastRefreshTime = 0
|
|
1149
|
+
else
|
|
1150
|
+
updatePointsBetweenRefreshes(deltaTime)
|
|
1151
|
+
end
|
|
1152
|
+
else
|
|
1153
|
+
updatePointsBetweenRefreshes(deltaTime)
|
|
1154
|
+
end
|
|
1155
|
+
|
|
1156
|
+
updateSegments(1, nil, scaledDelta, false)
|
|
1157
|
+
|
|
1158
|
+
if shapecast() then
|
|
1159
|
+
startDissipation()
|
|
1160
|
+
return nil
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
if elapsed >= lifetime then
|
|
1164
|
+
return nil
|
|
1165
|
+
end
|
|
1166
|
+
|
|
1167
|
+
return getSpeedDelta(deltaTime, true)
|
|
1168
|
+
end, speedTween, scope, utility.RENDER_PRIORITY + scope.depth)
|
|
1169
|
+
|
|
1170
|
+
startDissipation()
|
|
1171
|
+
end,
|
|
1172
|
+
true,
|
|
1173
|
+
utility.RENDER_PRIORITY + scope.depth
|
|
1174
|
+
)
|
|
1175
|
+
)
|
|
1176
|
+
end)
|
|
1177
|
+
)
|
|
1178
|
+
end
|
|
1179
|
+
|
|
1180
|
+
TS.Promise.all(promises):await()
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
return lightning
|