@rbxts/vfx-forge 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +82 -0
- package/README.md +39 -0
- package/out/effects/beam.luau +312 -0
- package/out/effects/bezier.luau +392 -0
- package/out/effects/camera_shake.luau +200 -0
- package/out/effects/lightning.luau +1183 -0
- package/out/effects/mesh.luau +466 -0
- package/out/effects/particle.luau +64 -0
- package/out/effects/randomizer.luau +110 -0
- package/out/effects/screen.luau +61 -0
- package/out/effects/shockwave_debris.luau +277 -0
- package/out/effects/shockwave_line.luau +356 -0
- package/out/effects/shockwave_ring.luau +252 -0
- package/out/effects/sound.luau +311 -0
- package/out/effects/spin.luau +88 -0
- package/out/effects/tweener.luau +122 -0
- package/out/emitters.luau +387 -0
- package/out/index.d.ts +341 -0
- package/out/init.luau +279 -0
- package/out/mod/attributes.luau +227 -0
- package/out/mod/color/Oklab.luau +93 -0
- package/out/mod/color/sRGB.luau +71 -0
- package/out/mod/common/bezier.luau +372 -0
- package/out/mod/common/flipbook.luau +102 -0
- package/out/mod/lerp.luau +210 -0
- package/out/mod/logger.luau +20 -0
- package/out/mod/shape.luau +207 -0
- package/out/mod/tween.luau +161 -0
- package/out/mod/utility.luau +707 -0
- package/out/obj/Bezier.luau +268 -0
- package/out/obj/ObjectCache.luau +289 -0
- package/out/services/caches.luau +62 -0
- package/out/services/effects.luau +234 -0
- package/out/services/enabled_effects.luau +120 -0
- package/out/services/texture_loader.luau +174 -0
- package/out/tsconfig.tsbuildinfo +1 -0
- package/out/types.luau +43 -0
- package/package.json +63 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
--!nolint LocalShadow
|
|
2
|
+
local CollectionService = game:GetService("CollectionService")
|
|
3
|
+
|
|
4
|
+
local attr = require("@mod/attributes")
|
|
5
|
+
local shape = require("@mod/shape")
|
|
6
|
+
local tween = require("@mod/tween")
|
|
7
|
+
local utility = require("@mod/utility")
|
|
8
|
+
|
|
9
|
+
local Oklab = require("@mod/color/Oklab")
|
|
10
|
+
local Bezier = require("@obj/Bezier")
|
|
11
|
+
|
|
12
|
+
local common = {}
|
|
13
|
+
|
|
14
|
+
common.drawFuncMap = {
|
|
15
|
+
Box = {
|
|
16
|
+
Volume = shape.getPointWithinBox,
|
|
17
|
+
Surface = shape.getPointOnBox,
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
Cylinder = {
|
|
21
|
+
Volume = function(seed, cframe, size, normal, partial)
|
|
22
|
+
return shape.getPointWithinCylinder(seed, 0, partial, cframe, size, normal)
|
|
23
|
+
end,
|
|
24
|
+
|
|
25
|
+
Surface = function(seed, cframe, size, normal, partial)
|
|
26
|
+
return shape.getPointWithinCylinder(seed, 1, partial, cframe, size, normal)
|
|
27
|
+
end,
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
Sphere = {
|
|
31
|
+
Volume = function(seed, cframe, size, normal, partial)
|
|
32
|
+
return shape.getPointWithinSphere(seed, 0, partial, cframe, size, normal)
|
|
33
|
+
end,
|
|
34
|
+
|
|
35
|
+
Surface = function(seed, cframe, size, normal, partial)
|
|
36
|
+
return shape.getPointWithinSphere(seed, 1, partial, cframe, size, normal)
|
|
37
|
+
end,
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
Disc = {
|
|
41
|
+
Volume = function(seed, cframe, size, normal, partial)
|
|
42
|
+
return shape.getPointWithinDisc(seed, 0, partial, cframe, size, normal)
|
|
43
|
+
end,
|
|
44
|
+
|
|
45
|
+
Surface = function(seed, cframe, size, normal, partial)
|
|
46
|
+
return shape.getPointWithinDisc(seed, 1, partial, cframe, size, normal)
|
|
47
|
+
end,
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function common.getColorAtTime(sequence: ColorSequence, time: number)
|
|
52
|
+
local keypoints = sequence.Keypoints
|
|
53
|
+
|
|
54
|
+
if time <= keypoints[1].Time then
|
|
55
|
+
return keypoints[1].Value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if time >= keypoints[#keypoints].Time then
|
|
59
|
+
return keypoints[#keypoints].Value
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
local closestBelow
|
|
63
|
+
local closestAbove
|
|
64
|
+
|
|
65
|
+
for i = 1, #keypoints do
|
|
66
|
+
local kp = keypoints[i]
|
|
67
|
+
if kp.Time == time then
|
|
68
|
+
return kp.Value
|
|
69
|
+
elseif kp.Time < time then
|
|
70
|
+
closestBelow = kp
|
|
71
|
+
elseif kp.Time > time then
|
|
72
|
+
closestAbove = kp
|
|
73
|
+
break
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if not closestBelow or not closestAbove then
|
|
78
|
+
return keypoints[1].Value
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
local alpha = (time - closestBelow.Time) / (closestAbove.Time - closestBelow.Time)
|
|
82
|
+
return closestBelow.Value:Lerp(closestAbove.Value, alpha)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
function common.getColorAtTimeOklab(sequence: ColorSequence, time: number)
|
|
86
|
+
local keypoints = sequence.Keypoints
|
|
87
|
+
|
|
88
|
+
if time <= keypoints[1].Time then
|
|
89
|
+
return keypoints[1].Value
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if time >= keypoints[#keypoints].Time then
|
|
93
|
+
return keypoints[#keypoints].Value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
local closestBelow
|
|
97
|
+
local closestAbove
|
|
98
|
+
|
|
99
|
+
for i = 1, #keypoints do
|
|
100
|
+
local kp = keypoints[i]
|
|
101
|
+
if kp.Time == time then
|
|
102
|
+
return kp.Value
|
|
103
|
+
elseif kp.Time < time then
|
|
104
|
+
closestBelow = kp
|
|
105
|
+
elseif kp.Time > time then
|
|
106
|
+
closestAbove = kp
|
|
107
|
+
break
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if not closestBelow or not closestAbove then
|
|
112
|
+
return keypoints[1].Value
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
local alpha = (time - closestBelow.Time) / (closestAbove.Time - closestBelow.Time)
|
|
116
|
+
|
|
117
|
+
local labA = Oklab.fromSRGB(closestBelow.Value)
|
|
118
|
+
local labB = Oklab.fromSRGB(closestAbove.Value)
|
|
119
|
+
|
|
120
|
+
local blendedLab = labA:Lerp(labB, alpha)
|
|
121
|
+
|
|
122
|
+
return Oklab.toSRGB(blendedLab)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
function common.getColorWithEasingOklab(sequence: ColorSequence, time: number, easingBezier)
|
|
126
|
+
local easedTime = 1 - easingBezier:getEase(math.clamp(time, 0, 1)).y
|
|
127
|
+
return common.getColorAtTimeOklab(sequence, math.clamp(easedTime, 0, 1))
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
export type HitboxConfig = {
|
|
131
|
+
enabled: boolean,
|
|
132
|
+
collisionGroup: string,
|
|
133
|
+
filterTag: string,
|
|
134
|
+
filterType: string,
|
|
135
|
+
ignoreCanCollide: boolean,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function common.createHitboxParams(config: HitboxConfig, parent: Instance, root: Attachment?): OverlapParams
|
|
139
|
+
local params = OverlapParams.new()
|
|
140
|
+
params.MaxParts = 1
|
|
141
|
+
params.FilterType = Enum.RaycastFilterType[config.filterType]
|
|
142
|
+
params.CollisionGroup = config.collisionGroup
|
|
143
|
+
params.RespectCanCollide = not config.ignoreCanCollide
|
|
144
|
+
|
|
145
|
+
params:AddToFilter(CollectionService:GetTagged(config.filterTag))
|
|
146
|
+
|
|
147
|
+
if config.filterType == "Exclude" then
|
|
148
|
+
local ancestorPart = root and root:FindFirstAncestorOfClass("Part")
|
|
149
|
+
params:AddToFilter({ workspace.Terrain, parent, ancestorPart })
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
return params
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
export type EmissionConfig = {
|
|
156
|
+
face: "InAndOut" | "Inward" | "Outward",
|
|
157
|
+
spreadAngle: Vector3,
|
|
158
|
+
mirror: boolean,
|
|
159
|
+
mirrorRot: Vector3,
|
|
160
|
+
partial: number,
|
|
161
|
+
emissionDirection: Enum.NormalId,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function common.calculateEmissionCFrame(
|
|
165
|
+
originCFrame: CFrame,
|
|
166
|
+
originSize: Vector3,
|
|
167
|
+
config: EmissionConfig,
|
|
168
|
+
drawFunc: ((...any) -> CFrame)?,
|
|
169
|
+
rng: Random,
|
|
170
|
+
endPoint: Attachment?,
|
|
171
|
+
isAttachment: boolean
|
|
172
|
+
)
|
|
173
|
+
local cf: CFrame
|
|
174
|
+
|
|
175
|
+
if isAttachment then
|
|
176
|
+
cf = originCFrame * CFrame.new(Vector3.zero, Vector3.FromNormalId(config.emissionDirection)).Rotation
|
|
177
|
+
elseif drawFunc then
|
|
178
|
+
cf = drawFunc(nil, originCFrame, originSize, config.emissionDirection, config.partial)
|
|
179
|
+
else
|
|
180
|
+
cf = originCFrame
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
local normal = Vector3.FromNormalId(config.emissionDirection)
|
|
184
|
+
local pitchAxis = normal:Cross(originCFrame.LookVector)
|
|
185
|
+
|
|
186
|
+
if pitchAxis.Magnitude < 0.001 then
|
|
187
|
+
pitchAxis = normal:Cross(originCFrame.UpVector)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
local spread = CFrame.fromAxisAngle(normal, math.rad(rng:NextNumber(-config.spreadAngle.X, config.spreadAngle.X)))
|
|
191
|
+
* CFrame.fromAxisAngle(pitchAxis, math.rad(rng:NextNumber(-config.spreadAngle.Y, config.spreadAngle.Y)))
|
|
192
|
+
|
|
193
|
+
cf *= spread
|
|
194
|
+
|
|
195
|
+
if config.face == "Inward" or (config.face == "InAndOut" and rng:NextInteger(0, 1) == 1) then
|
|
196
|
+
cf *= CFrame.fromOrientation(0, math.pi, 0)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
if endPoint and config.mirror and (cf.Position - endPoint.WorldPosition).Unit:Dot(cf.RightVector) >= 0 then
|
|
200
|
+
local rot = config.mirrorRot * utility.DEG_TO_RAD
|
|
201
|
+
cf *= CFrame.fromOrientation(rot.X, rot.Y, rot.Z)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
return cf
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
function common.createBezierWithEndpoint(points: { vector }, cf: CFrame, endPoint: Attachment?, endT1: Attachment?)
|
|
208
|
+
if not endPoint then
|
|
209
|
+
return Bezier.new(points)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
local pts = {}
|
|
213
|
+
|
|
214
|
+
for j, pt in points do
|
|
215
|
+
table.insert(
|
|
216
|
+
pts,
|
|
217
|
+
if j == (#points - 1)
|
|
218
|
+
then endT1 and endT1.WorldPosition or endPoint.WorldPosition
|
|
219
|
+
else if j == #points then endPoint.WorldPosition else cf * (pt - points[1])
|
|
220
|
+
)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
return Bezier.new(pts)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
function common.createPosGetter(
|
|
227
|
+
bezier,
|
|
228
|
+
points: { vector },
|
|
229
|
+
cf: CFrame,
|
|
230
|
+
endPoint: Attachment?,
|
|
231
|
+
useArcSpace: boolean?
|
|
232
|
+
): (number) -> Vector3
|
|
233
|
+
return function(alpha: number): Vector3
|
|
234
|
+
local p = if useArcSpace ~= false then bezier:getPositionArcSpace(alpha) else bezier:getPosition(alpha)
|
|
235
|
+
|
|
236
|
+
if endPoint then
|
|
237
|
+
return p
|
|
238
|
+
else
|
|
239
|
+
return cf * (p - points[1])
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
function common.readCommonAttributes(ref: Attachment)
|
|
245
|
+
return {
|
|
246
|
+
-- timing
|
|
247
|
+
emitDelay = attr.get(ref, "EmitDelay", 0),
|
|
248
|
+
emitCount = attr.get(ref, "EmitCount", 1),
|
|
249
|
+
emitDuration = attr.get(ref, "EmitDuration", 0),
|
|
250
|
+
destroyDelay = attr.get(ref, "DestroyDelay", 0),
|
|
251
|
+
duration = attr.getRange(ref, "Duration", NumberRange.new(1, 1), NumberRange.new(0, math.huge)),
|
|
252
|
+
|
|
253
|
+
-- shape/emission
|
|
254
|
+
shapeType = attr.getEnum(ref, "Shape", "Box", { "Box", "Cylinder", "Sphere", "Disc" }),
|
|
255
|
+
shapeStyle = attr.getEnum(ref, "ShapeStyle", "Volume", { "Volume", "Surface" }),
|
|
256
|
+
emissionDirection = Enum.NormalId[attr.getEnum(
|
|
257
|
+
ref,
|
|
258
|
+
"EmissionDirection",
|
|
259
|
+
"Top",
|
|
260
|
+
{ "Top", "Bottom", "Left", "Right", "Front", "Back" }
|
|
261
|
+
)],
|
|
262
|
+
face = attr.getEnum(ref, "ShapeFace", "Outward", { "InAndOut", "Inward", "Outward" }),
|
|
263
|
+
spreadAngle = attr.get(ref, "SpreadAngle", vector.zero),
|
|
264
|
+
partial = attr.get(ref, "ShapePartial", 1),
|
|
265
|
+
|
|
266
|
+
-- path
|
|
267
|
+
syncPosition = attr.get(ref, "SyncPosition", false),
|
|
268
|
+
mirror = attr.get(ref, "MirrorPaths", true),
|
|
269
|
+
mirrorRot = attr.get(ref, "MirrorRotation", vector.create(0, 0, 180)),
|
|
270
|
+
|
|
271
|
+
-- projectile
|
|
272
|
+
projectileEnabled = attr.get(ref, "ProjectileEnabled", false),
|
|
273
|
+
projectileMatchEnd = attr.get(ref, "MatchEndDirection", false),
|
|
274
|
+
projectileSpeed = attr.get(ref, "ProjectileSpeed", 30),
|
|
275
|
+
projectileLifetime = attr.getRange(ref, "ProjectileLifetime", NumberRange.new(1, 1), NumberRange.new(0, math.huge)),
|
|
276
|
+
|
|
277
|
+
-- hitbox
|
|
278
|
+
hitboxEnabled = attr.get(ref, "HitboxEnabled", false),
|
|
279
|
+
hitboxCollisionGroup = attr.get(ref, "HitboxCollisionGroup", "Default"),
|
|
280
|
+
hitboxFilterTag = attr.get(ref, "HitboxFilterTag", ""),
|
|
281
|
+
hitboxFilterType = attr.get(ref, "HitboxFilterType", "Exclude"),
|
|
282
|
+
hitboxIgnoreCanCollide = attr.get(ref, "HitboxIgnoreCanCollide", false),
|
|
283
|
+
|
|
284
|
+
-- speed
|
|
285
|
+
speedStart = attr.get(ref, "Speed_Start", 1),
|
|
286
|
+
speedEnd = attr.get(ref, "Speed_End", 1),
|
|
287
|
+
}
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
function common.getCurrentOriginCFrame(parent: Instance, fallback: CFrame)
|
|
291
|
+
if parent:IsA("BasePart") then
|
|
292
|
+
return parent.CFrame
|
|
293
|
+
elseif parent:IsA("Attachment") then
|
|
294
|
+
return parent.WorldCFrame
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
return fallback
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
function common.findEndAttachments(ref: Attachment): (Attachment?, Attachment?)
|
|
301
|
+
local endPoint = ref:FindFirstChild("End")
|
|
302
|
+
local endT1 = endPoint and endPoint:FindFirstChild("T1")
|
|
303
|
+
|
|
304
|
+
if endPoint and not endPoint:IsA("Attachment") then
|
|
305
|
+
endPoint = nil
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
if endT1 and not endT1:IsA("Attachment") then
|
|
309
|
+
endT1 = nil
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
return endPoint, endT1
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
function common.validateParent(ref: Attachment): Instance?
|
|
316
|
+
local parent = ref.Parent
|
|
317
|
+
|
|
318
|
+
if not parent then
|
|
319
|
+
return nil
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
if not parent:IsA("BasePart") and not parent:IsA("Attachment") then
|
|
323
|
+
parent = ref
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
return parent
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
function common.getPerpendicularVectors(tangent: Vector3)
|
|
330
|
+
local right = tangent:Cross(Vector3.yAxis)
|
|
331
|
+
|
|
332
|
+
if right.Magnitude < 0.001 then
|
|
333
|
+
right = tangent:Cross(Vector3.zAxis)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
right = right.Unit
|
|
337
|
+
|
|
338
|
+
local up = tangent:Cross(right).Unit
|
|
339
|
+
|
|
340
|
+
return right, up
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
function common.createPropertyTween(
|
|
344
|
+
scope: any,
|
|
345
|
+
ref: Attachment,
|
|
346
|
+
attrName: string,
|
|
347
|
+
tweenDuration: number,
|
|
348
|
+
startVal: number,
|
|
349
|
+
endVal: number,
|
|
350
|
+
setter: (number) -> (),
|
|
351
|
+
getSpeed: () -> number,
|
|
352
|
+
speedTween: any?
|
|
353
|
+
)
|
|
354
|
+
if startVal == endVal then
|
|
355
|
+
return
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
table.insert(
|
|
359
|
+
scope,
|
|
360
|
+
tween.fromParams(
|
|
361
|
+
attr.get(ref, attrName .. "_Curve", utility.default_bezier),
|
|
362
|
+
tweenDuration,
|
|
363
|
+
function(alpha, deltaTime)
|
|
364
|
+
setter(utility.lerp(startVal, endVal, alpha))
|
|
365
|
+
return deltaTime * getSpeed()
|
|
366
|
+
end,
|
|
367
|
+
speedTween
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
return common
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
local RunService = game:GetService("RunService")
|
|
2
|
+
local CollectionService = game:GetService("CollectionService")
|
|
3
|
+
|
|
4
|
+
local common = {}
|
|
5
|
+
|
|
6
|
+
function common.serialize(ids: { number })
|
|
7
|
+
local buf = buffer.create(#ids * 8)
|
|
8
|
+
|
|
9
|
+
for i, id in ids do
|
|
10
|
+
buffer.writef64(buf, (i - 1) * 8, id)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
return buf
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
function common.deserialize(data: string | buffer)
|
|
17
|
+
local buf = typeof(data) == "string" and buffer.fromstring(data) or data
|
|
18
|
+
local count = buffer.len(buf) / 8
|
|
19
|
+
|
|
20
|
+
local ids = {}
|
|
21
|
+
|
|
22
|
+
for i = 0, count - 1 do
|
|
23
|
+
table.insert(ids, buffer.readf64(buf, i * 8))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
return ids
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
function common.isLocalFlipbook(ref: Instance)
|
|
30
|
+
if not RunService:IsStudio() then
|
|
31
|
+
return false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
for _, tag in CollectionService:GetTags(ref) do
|
|
35
|
+
if tag:match("^_local_flipbook_") then
|
|
36
|
+
return true
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
return false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
function common.getTexturePrefix(ref: Instance)
|
|
44
|
+
return common.isLocalFlipbook(ref) and "rbxtemp://" or "rbxassetid://"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
function common.getFlipbookData(ref: Instance): { number }?
|
|
48
|
+
if not ref:GetAttribute("FlipbookEnabled") then
|
|
49
|
+
return nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
local data = ref:GetAttribute("FlipbookTextures")
|
|
53
|
+
|
|
54
|
+
if not data then
|
|
55
|
+
return nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
local frames = common.deserialize(data)
|
|
59
|
+
|
|
60
|
+
if #frames == 0 then
|
|
61
|
+
return nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
return frames
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
export type FlipbookConfig = {
|
|
68
|
+
ref: Instance,
|
|
69
|
+
curve: string,
|
|
70
|
+
speedTween: any?,
|
|
71
|
+
|
|
72
|
+
duration: number,
|
|
73
|
+
effectDuration: number,
|
|
74
|
+
|
|
75
|
+
frames: { number },
|
|
76
|
+
|
|
77
|
+
getSpeed: () -> number,
|
|
78
|
+
setTexture: (texture: string) -> (),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function common.getChangeDuration(config: FlipbookConfig)
|
|
82
|
+
local changeDuration = config.duration
|
|
83
|
+
|
|
84
|
+
if config.ref:GetAttribute("SyncDuration") then
|
|
85
|
+
changeDuration = config.effectDuration
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return changeDuration
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
function common.createUpdateCallback(config: FlipbookConfig)
|
|
92
|
+
local prefix = common.getTexturePrefix(config.ref)
|
|
93
|
+
|
|
94
|
+
return function(alpha, deltaTime)
|
|
95
|
+
local index = math.max(math.round(#config.frames * alpha), 1)
|
|
96
|
+
config.setTexture(`{prefix}{config.frames[index]}`)
|
|
97
|
+
|
|
98
|
+
return deltaTime * config.getSpeed()
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
return common
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
local Oklab = require("@mod/color/Oklab")
|
|
2
|
+
|
|
3
|
+
local MAX_KEYPOINTS = 20
|
|
4
|
+
|
|
5
|
+
local lerp = {}
|
|
6
|
+
|
|
7
|
+
function lerp.number(a: number, b: number, t: number)
|
|
8
|
+
return a + (b - a) * t
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
function lerp.Vector3(a: Vector3, b: Vector3, t: number)
|
|
12
|
+
return a:Lerp(b, t)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
function lerp.Vector2(a: Vector2, b: Vector2, t: number)
|
|
16
|
+
return a:Lerp(b, t)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
function lerp.CFrame(a: CFrame, b: CFrame, t: number)
|
|
20
|
+
return a:Lerp(b, t)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
function lerp.UDim2(a: UDim2, b: UDim2, t: number)
|
|
24
|
+
return a:Lerp(b, t)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
function lerp.UDim(a: UDim, b: UDim, t: number)
|
|
28
|
+
-- stylua: ignore
|
|
29
|
+
return UDim.new(
|
|
30
|
+
lerp.number(a.Scale, b.Scale, t),
|
|
31
|
+
lerp.number(a.Offset, b.Offset, t)
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
function lerp.NumberRange(a: NumberRange, b: NumberRange, t: number)
|
|
36
|
+
-- stylua: ignore
|
|
37
|
+
return NumberRange.new(
|
|
38
|
+
lerp.number(a.Min, b.Min, t),
|
|
39
|
+
lerp.number(a.Max, b.Max, t)
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
function lerp.Color3(a: Color3, b: Color3, t: number)
|
|
44
|
+
-- stylua: ignore
|
|
45
|
+
return Oklab.toSRGB(
|
|
46
|
+
Oklab.fromSRGB(a):Lerp(Oklab.fromSRGB(b), t)
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
function lerp.PhysicalProperties(a: PhysicalProperties, b: PhysicalProperties, t: number)
|
|
51
|
+
return PhysicalProperties.new(
|
|
52
|
+
lerp.number(a.Density, b.Density, t),
|
|
53
|
+
lerp.number(a.Friction, b.Friction, t),
|
|
54
|
+
lerp.number(a.Elasticity, b.Elasticity, t),
|
|
55
|
+
lerp.number(a.FrictionWeight, b.FrictionWeight, t),
|
|
56
|
+
lerp.number(a.ElasticityWeight, b.ElasticityWeight, t)
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
function lerp.Rect(a: Rect, b: Rect, t: number)
|
|
61
|
+
return Rect.new(a.Min:Lerp(b.Min, t), a.Max:Lerp(b.Max, t))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
-- credit https://github.com/boatbomber/BoatTween
|
|
65
|
+
function lerp.NumberSequence(a: NumberSequence, b: NumberSequence, t: number)
|
|
66
|
+
local keypoints = {}
|
|
67
|
+
local addedTimes = {}
|
|
68
|
+
|
|
69
|
+
local keylength = 0
|
|
70
|
+
|
|
71
|
+
for _, ap in a.Keypoints do
|
|
72
|
+
local closestAbove, closestBelow
|
|
73
|
+
|
|
74
|
+
for _, bp in b.Keypoints do
|
|
75
|
+
if bp.Time == ap.Time then
|
|
76
|
+
closestAbove, closestBelow = bp, bp
|
|
77
|
+
break
|
|
78
|
+
elseif bp.Time < ap.Time and (closestBelow == nil or bp.Time > closestBelow.Time) then
|
|
79
|
+
closestBelow = bp
|
|
80
|
+
elseif bp.Time > ap.Time and (closestAbove == nil or bp.Time < closestAbove.Time) then
|
|
81
|
+
closestAbove = bp
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
local bValue, bEnvelope
|
|
86
|
+
|
|
87
|
+
if closestAbove == closestBelow then
|
|
88
|
+
bValue, bEnvelope = closestAbove.Value, closestAbove.Envelope
|
|
89
|
+
else
|
|
90
|
+
local p = (ap.Time - closestBelow.Time) / (closestAbove.Time - closestBelow.Time)
|
|
91
|
+
bValue = (closestAbove.Value - closestBelow.Value) * p + closestBelow.Value
|
|
92
|
+
bEnvelope = (closestAbove.Envelope - closestBelow.Envelope) * p + closestBelow.Envelope
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
keylength += 1
|
|
96
|
+
keypoints[keylength] = NumberSequenceKeypoint.new(
|
|
97
|
+
ap.Time,
|
|
98
|
+
(bValue - ap.Value) * t + ap.Value,
|
|
99
|
+
(bEnvelope - ap.Envelope) * t + ap.Envelope
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
addedTimes[ap.Time] = true
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
for _, bp in b.Keypoints do
|
|
106
|
+
if not addedTimes[bp.Time] then
|
|
107
|
+
local closestAbove, closestBelow
|
|
108
|
+
|
|
109
|
+
for _, ap in a.Keypoints do
|
|
110
|
+
if ap.Time == bp.Time then
|
|
111
|
+
closestAbove, closestBelow = ap, ap
|
|
112
|
+
break
|
|
113
|
+
elseif ap.Time < bp.Time and (closestBelow == nil or ap.Time > closestBelow.Time) then
|
|
114
|
+
closestBelow = ap
|
|
115
|
+
elseif ap.Time > bp.Time and (closestAbove == nil or ap.Time < closestAbove.Time) then
|
|
116
|
+
closestAbove = ap
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
local aValue, aEnvelope
|
|
121
|
+
|
|
122
|
+
if closestAbove == closestBelow then
|
|
123
|
+
aValue, aEnvelope = closestAbove.Value, closestAbove.Envelope
|
|
124
|
+
else
|
|
125
|
+
local p = (bp.Time - closestBelow.Time) / (closestAbove.Time - closestBelow.Time)
|
|
126
|
+
aValue = (closestAbove.Value - closestBelow.Value) * p + closestBelow.Value
|
|
127
|
+
aEnvelope = (closestAbove.Envelope - closestBelow.Envelope) * p + closestBelow.Envelope
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
keylength += 1
|
|
131
|
+
keypoints[keylength] =
|
|
132
|
+
NumberSequenceKeypoint.new(bp.Time, (bp.Value - aValue) * t + aValue, (bp.Envelope - aEnvelope) * t + aEnvelope)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
table.sort(keypoints, function(a, b)
|
|
137
|
+
return a.Time < b.Time
|
|
138
|
+
end)
|
|
139
|
+
|
|
140
|
+
local finalKeypoints
|
|
141
|
+
|
|
142
|
+
if #keypoints > MAX_KEYPOINTS then
|
|
143
|
+
finalKeypoints = {}
|
|
144
|
+
local step = (#keypoints - 1) / (MAX_KEYPOINTS - 1)
|
|
145
|
+
|
|
146
|
+
for i = 0, MAX_KEYPOINTS - 1 do
|
|
147
|
+
local index = math.floor(i * step + 1)
|
|
148
|
+
table.insert(finalKeypoints, keypoints[index])
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if finalKeypoints[#finalKeypoints].Time < keypoints[#keypoints].Time then
|
|
152
|
+
finalKeypoints[#finalKeypoints] = keypoints[#keypoints]
|
|
153
|
+
end
|
|
154
|
+
else
|
|
155
|
+
finalKeypoints = keypoints
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
return NumberSequence.new(finalKeypoints)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
local function getColorAtTime(sequence: ColorSequence, time: number)
|
|
162
|
+
local keypoints = sequence.Keypoints
|
|
163
|
+
|
|
164
|
+
if time <= keypoints[1].Time then
|
|
165
|
+
return keypoints[1].Value
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
if time >= keypoints[#keypoints].Time then
|
|
169
|
+
return keypoints[#keypoints].Value
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
local closestBelow
|
|
173
|
+
local closestAbove
|
|
174
|
+
|
|
175
|
+
for i = 1, #keypoints do
|
|
176
|
+
local kp = keypoints[i]
|
|
177
|
+
if kp.Time == time then
|
|
178
|
+
return kp.Value
|
|
179
|
+
elseif kp.Time < time then
|
|
180
|
+
closestBelow = kp
|
|
181
|
+
elseif kp.Time > time then
|
|
182
|
+
closestAbove = kp
|
|
183
|
+
break
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
local alpha = (time - closestBelow.Time) / (closestAbove.Time - closestBelow.Time)
|
|
188
|
+
|
|
189
|
+
return closestBelow.Value:Lerp(closestAbove.Value, alpha)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
function lerp.ColorSequence(a: ColorSequence, b: ColorSequence, t: number)
|
|
193
|
+
local newKeypoints = {}
|
|
194
|
+
|
|
195
|
+
for _, bp in ipairs(b.Keypoints) do
|
|
196
|
+
local aValueAtBTime = getColorAtTime(a, bp.Time)
|
|
197
|
+
-- use linear space because color sequences are in linear space anyway
|
|
198
|
+
local finalColor = Oklab.toSRGB(Oklab.fromSRGB(aValueAtBTime:Lerp(bp.Value, t)))
|
|
199
|
+
|
|
200
|
+
table.insert(newKeypoints, ColorSequenceKeypoint.new(bp.Time, finalColor))
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
return ColorSequence.new(newKeypoints)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
function lerp.Other(a: any, b: any, t: number)
|
|
207
|
+
return t < 0.5 and a or b
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
return lerp
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
local function msg(...: string)
|
|
2
|
+
return `[Forge Emit API]: {table.concat({ ... }, " ")}`
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
local logger = {}
|
|
6
|
+
|
|
7
|
+
function logger.error(...)
|
|
8
|
+
error(msg(..., "\n"))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
function logger.warn(...)
|
|
12
|
+
warn(msg(...))
|
|
13
|
+
warn(msg(debug.traceback("stack trace:")))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
function logger.info(...)
|
|
17
|
+
print(msg(...))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
return logger
|