@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,252 @@
|
|
|
1
|
+
local CollectionService = game:GetService("CollectionService")
|
|
2
|
+
|
|
3
|
+
local attr = require("@mod/attributes")
|
|
4
|
+
local tween = require("@mod/tween")
|
|
5
|
+
local types = require("@root/types")
|
|
6
|
+
local utility = require("@mod/utility")
|
|
7
|
+
|
|
8
|
+
local Promise = require("@pkg/Promise")
|
|
9
|
+
local ObjectCache = require("@obj/ObjectCache")
|
|
10
|
+
|
|
11
|
+
local rng = Random.new()
|
|
12
|
+
|
|
13
|
+
local ring = {}
|
|
14
|
+
|
|
15
|
+
local part_cache: ObjectCache.ObjectCache?
|
|
16
|
+
|
|
17
|
+
function ring.init(cache)
|
|
18
|
+
part_cache = cache
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
function ring.deinit()
|
|
22
|
+
part_cache = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
function ring.emit(origin: Attachment, ref: Part, scope: types.scope)
|
|
26
|
+
if not part_cache then
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
local raydir = attr.get(origin, "RayDirection", vector.create(0, -50, 0))
|
|
31
|
+
|
|
32
|
+
local rayColGroup = attr.get(origin, "RayCollisionGroup", "Default")
|
|
33
|
+
local rayFilterTag = attr.get(origin, "FilterTag", "")
|
|
34
|
+
local rayFilterType = attr.get(origin, "FilterType", "Exclude")
|
|
35
|
+
|
|
36
|
+
local rayIgnoreWater = attr.get(origin, "IgnoreWater", true)
|
|
37
|
+
local rayIgnoreCanCollide = attr.get(origin, "IgnoreCanCollide", false)
|
|
38
|
+
|
|
39
|
+
local params = RaycastParams.new()
|
|
40
|
+
params.CollisionGroup = rayColGroup
|
|
41
|
+
params.IgnoreWater = rayIgnoreWater
|
|
42
|
+
params.RespectCanCollide = not rayIgnoreCanCollide
|
|
43
|
+
params.FilterType = Enum.RaycastFilterType[rayFilterType]
|
|
44
|
+
params.FilterDescendantsInstances = CollectionService:GetTagged(rayFilterTag)
|
|
45
|
+
|
|
46
|
+
if rayFilterType == "Exclude" then
|
|
47
|
+
params:AddToFilter({ workspace.Terrain })
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
local emitDelay = attr.get(ref, "EmitDelay", 0)
|
|
51
|
+
|
|
52
|
+
local radius = attr.get(ref, "Radius", 5)
|
|
53
|
+
local segments = attr.get(ref, "Segments", 7)
|
|
54
|
+
|
|
55
|
+
local lifetime = attr.getRange(ref, "Lifetime", NumberRange.new(2, 3), NumberRange.new(0, math.huge))
|
|
56
|
+
|
|
57
|
+
local old_partOffset = attr.get(ref, "PartOffset", vector.zero, true)
|
|
58
|
+
|
|
59
|
+
local baseOffset = attr.get(ref, "BaseOffset", old_partOffset)
|
|
60
|
+
local offsetStart = attr.get(ref, "Offset_Start", old_partOffset)
|
|
61
|
+
local offsetEnd = attr.get(ref, "Offset_End", old_partOffset)
|
|
62
|
+
|
|
63
|
+
local offsetStartDuration = attr.get(ref, "Offset_Start_Duration", 0.5)
|
|
64
|
+
local offsetEndDuration = attr.get(ref, "Offset_End_Duration", 0.5)
|
|
65
|
+
|
|
66
|
+
local sizeScaleStart = attr.get(ref, "SizeScaleStart", vector.zero)
|
|
67
|
+
local sizeScaleEnd = attr.get(ref, "SizeScaleEnd", vector.zero)
|
|
68
|
+
|
|
69
|
+
local minSize = attr.get(ref, "MinSize", vector.create(2, 1, 2))
|
|
70
|
+
local maxSize = attr.get(ref, "MaxSize", vector.create(3, 2, 3))
|
|
71
|
+
|
|
72
|
+
local old_sizeCurve = attr.get(ref, "Size_Curve", utility.default_bezier, true)
|
|
73
|
+
local old_sizeDuration = attr.get(ref, "Size_Duration", 0.5, true)
|
|
74
|
+
|
|
75
|
+
local sizeStartCurve = attr.get(ref, "Size_Start_Curve", old_sizeCurve)
|
|
76
|
+
local sizeEndCurve = attr.get(ref, "Size_End_Curve", old_sizeCurve)
|
|
77
|
+
|
|
78
|
+
local sizeStartDuration = attr.get(ref, "Size_Start_Duration", old_sizeDuration)
|
|
79
|
+
local sizeEndDuration = attr.get(ref, "Size_End_Duration", old_sizeDuration)
|
|
80
|
+
|
|
81
|
+
local tpDuration = attr.get(ref, "Transparency_Duration", 0.5)
|
|
82
|
+
|
|
83
|
+
local tpStart = attr.get(ref, "Transparency_Start", 0)
|
|
84
|
+
local tpEnd = attr.get(ref, "Transparency_End", 0)
|
|
85
|
+
|
|
86
|
+
task.wait(emitDelay)
|
|
87
|
+
|
|
88
|
+
local promises = {}
|
|
89
|
+
local originCFrame = utility.getTransformedOriginExtents(origin)
|
|
90
|
+
|
|
91
|
+
for i = 0, segments - 1 do
|
|
92
|
+
local angle = i / segments * math.pi * 2
|
|
93
|
+
|
|
94
|
+
local x = radius * math.cos(angle)
|
|
95
|
+
local y = radius * math.sin(angle)
|
|
96
|
+
|
|
97
|
+
local result =
|
|
98
|
+
workspace:Raycast((originCFrame * CFrame.new(x, 0, y)).Position, originCFrame:VectorToWorldSpace(raydir), params)
|
|
99
|
+
|
|
100
|
+
if not result then
|
|
101
|
+
continue
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
local width = rng:NextNumber(minSize.X, maxSize.X)
|
|
105
|
+
|
|
106
|
+
local height = rng:NextNumber(minSize.Y, maxSize.Y)
|
|
107
|
+
local length = rng:NextNumber(minSize.Z, maxSize.Z)
|
|
108
|
+
|
|
109
|
+
local id = utility.getRandomId()
|
|
110
|
+
|
|
111
|
+
local part = part_cache:get(id)
|
|
112
|
+
|
|
113
|
+
local realPart = part._getReal()
|
|
114
|
+
|
|
115
|
+
utility.copyProperties(ref, realPart, utility.COPY_PART_PROPERTIES)
|
|
116
|
+
utility.copyProperties(ref, realPart, utility.COPY_EXTENDED_PART_PROPERTIES)
|
|
117
|
+
|
|
118
|
+
if #ref:GetChildren() ~= 0 then
|
|
119
|
+
local clone = ref:Clone()
|
|
120
|
+
|
|
121
|
+
for _, child in clone:GetChildren() do
|
|
122
|
+
child.Parent = realPart
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
clone:Destroy()
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
local emitOnFinish = scope.effects.prepareEmitOnFinish(part, scope)
|
|
129
|
+
|
|
130
|
+
table.insert(scope, function()
|
|
131
|
+
if part_cache then
|
|
132
|
+
part_cache:free(id)
|
|
133
|
+
end
|
|
134
|
+
end)
|
|
135
|
+
|
|
136
|
+
part.Color = result.Instance.Color
|
|
137
|
+
part.Material = result.Material
|
|
138
|
+
part.Transparency = result.Instance.Transparency
|
|
139
|
+
|
|
140
|
+
if part.Transparency == 0 then
|
|
141
|
+
part.Transparency = tpStart
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
part.Size = Vector3.zero
|
|
145
|
+
|
|
146
|
+
local rx = -math.cos(angle)
|
|
147
|
+
local rz = -math.sin(angle)
|
|
148
|
+
|
|
149
|
+
local dir = originCFrame:VectorToWorldSpace(Vector3.new(rx, 0, rz))
|
|
150
|
+
local right = dir:Cross(result.Normal).Unit
|
|
151
|
+
|
|
152
|
+
local rotCF = CFrame.fromMatrix(result.Position, right, result.Normal)
|
|
153
|
+
* CFrame.fromOrientation(-math.atan(height / length), 0, 0)
|
|
154
|
+
|
|
155
|
+
part.CFrame = CFrame.new(offsetStart) * rotCF
|
|
156
|
+
|
|
157
|
+
local sizeTween
|
|
158
|
+
local offsetTween
|
|
159
|
+
|
|
160
|
+
local currentOffset = Vector3.zero
|
|
161
|
+
|
|
162
|
+
if offsetStart ~= baseOffset then
|
|
163
|
+
offsetTween = tween.fromParams(
|
|
164
|
+
attr.get(ref, "Offset_Start_Curve", utility.default_bezier),
|
|
165
|
+
offsetStartDuration,
|
|
166
|
+
function(alpha, deltaTime)
|
|
167
|
+
currentOffset = offsetStart:Lerp(baseOffset, alpha)
|
|
168
|
+
part.CFrame = CFrame.new(currentOffset) * rotCF
|
|
169
|
+
return deltaTime
|
|
170
|
+
end
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
table.insert(scope, sizeTween)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
local size = Vector3.new(width, height, length)
|
|
177
|
+
|
|
178
|
+
if size * sizeScaleStart ~= size then
|
|
179
|
+
sizeTween = tween.fromParams(sizeStartCurve, sizeStartDuration, function(alpha, deltaTime)
|
|
180
|
+
part.Size = (size * sizeScaleStart):Lerp(size, alpha)
|
|
181
|
+
return deltaTime
|
|
182
|
+
end)
|
|
183
|
+
|
|
184
|
+
table.insert(scope, sizeTween)
|
|
185
|
+
else
|
|
186
|
+
part.Size = size * sizeScaleStart
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
local env = scope.effects.emitNested(part, scope.depth + 1)
|
|
190
|
+
table.insert(promises, env.Finished)
|
|
191
|
+
|
|
192
|
+
task.delay(rng:NextNumber(lifetime.Min, lifetime.Max), function()
|
|
193
|
+
if sizeTween then
|
|
194
|
+
sizeTween:Disconnect()
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
if offsetTween then
|
|
198
|
+
offsetTween:Disconnect()
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
local startSize = part.Size
|
|
202
|
+
local startTp = part.Transparency
|
|
203
|
+
|
|
204
|
+
if startSize ~= size * sizeScaleEnd then
|
|
205
|
+
table.insert(
|
|
206
|
+
scope,
|
|
207
|
+
tween.fromParams(sizeEndCurve, sizeEndDuration, function(alpha, deltaTime)
|
|
208
|
+
part.Size = startSize:Lerp(size * sizeScaleEnd, alpha)
|
|
209
|
+
return deltaTime
|
|
210
|
+
end)
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
if currentOffset ~= offsetEnd then
|
|
215
|
+
table.insert(
|
|
216
|
+
scope,
|
|
217
|
+
tween.fromParams(
|
|
218
|
+
attr.get(ref, "Offset_End_Curve", utility.default_bezier),
|
|
219
|
+
offsetEndDuration,
|
|
220
|
+
function(alpha, deltaTime)
|
|
221
|
+
part.CFrame = CFrame.new(currentOffset:Lerp(offsetEnd, alpha)) * rotCF
|
|
222
|
+
return deltaTime
|
|
223
|
+
end
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
if startTp ~= tpEnd then
|
|
229
|
+
table.insert(
|
|
230
|
+
scope,
|
|
231
|
+
tween.fromParams(
|
|
232
|
+
attr.get(ref, "Transparency_Curve", utility.default_bezier),
|
|
233
|
+
tpDuration,
|
|
234
|
+
function(alpha, deltaTime)
|
|
235
|
+
part.Transparency = utility.lerp(startTp, tpEnd, alpha)
|
|
236
|
+
return deltaTime
|
|
237
|
+
end
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
local env = scope.effects.emitOnFinish(emitOnFinish, part, scope.depth + 1)
|
|
243
|
+
table.insert(promises, env.Finished)
|
|
244
|
+
end)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
task.wait(lifetime.Max + math.max(sizeEndDuration, offsetEndDuration))
|
|
248
|
+
|
|
249
|
+
Promise.all(promises):await()
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
return ring
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
local RunService = game:GetService("RunService")
|
|
2
|
+
local CollectionService = game:GetService("CollectionService")
|
|
3
|
+
|
|
4
|
+
local attr = require("@mod/attributes")
|
|
5
|
+
|
|
6
|
+
local tween = require("@mod/tween")
|
|
7
|
+
local types = require("@root/types")
|
|
8
|
+
local utility = require("@mod/utility")
|
|
9
|
+
local Promise = require("@pkg/Promise")
|
|
10
|
+
|
|
11
|
+
local rng = Random.new()
|
|
12
|
+
|
|
13
|
+
local sound = {}
|
|
14
|
+
|
|
15
|
+
local soundWidget: DockWidgetPluginGui?
|
|
16
|
+
|
|
17
|
+
local function selectRandomSound(ref: Sound): Sound?
|
|
18
|
+
local poolTag = attr.get(ref, "SoundPoolTag", "")
|
|
19
|
+
|
|
20
|
+
if poolTag == "" then
|
|
21
|
+
return ref
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
local sounds = CollectionService:GetTagged(poolTag)
|
|
25
|
+
|
|
26
|
+
local weights: { number } = {}
|
|
27
|
+
local validSounds: { Sound } = {}
|
|
28
|
+
|
|
29
|
+
local totalWeight = 0
|
|
30
|
+
|
|
31
|
+
for _, obj in sounds do
|
|
32
|
+
if not obj:IsA("Sound") then
|
|
33
|
+
continue
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
table.insert(validSounds, obj)
|
|
37
|
+
|
|
38
|
+
local weight = attr.get(obj, "SoundPoolWeight", 1)
|
|
39
|
+
|
|
40
|
+
table.insert(weights, weight)
|
|
41
|
+
totalWeight += weight
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if #validSounds == 0 then
|
|
45
|
+
return ref
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if totalWeight > 0 then
|
|
49
|
+
local roll = rng:NextNumber() * totalWeight
|
|
50
|
+
local accumulated = 0
|
|
51
|
+
|
|
52
|
+
for i, weight in weights do
|
|
53
|
+
accumulated += weight
|
|
54
|
+
|
|
55
|
+
if roll <= accumulated then
|
|
56
|
+
return validSounds[i]
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return validSounds[rng:NextInteger(1, #validSounds)]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
local function createSoundClone(ref: Sound, sourceSound: Sound, scope: types.scope): (Sound, Part?)
|
|
65
|
+
local clone = sourceSound:Clone()
|
|
66
|
+
|
|
67
|
+
for _, tag in clone:GetTags() do
|
|
68
|
+
clone:RemoveTag(tag)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
local parent = ref:FindFirstAncestorWhichIsA("BasePart")
|
|
72
|
+
|
|
73
|
+
local partClone
|
|
74
|
+
|
|
75
|
+
if utility.PLUGIN_CONTEXT then
|
|
76
|
+
if parent then
|
|
77
|
+
partClone = Instance.new("Part")
|
|
78
|
+
|
|
79
|
+
partClone.Name = "SoundEmitter"
|
|
80
|
+
partClone.Anchored = true
|
|
81
|
+
partClone.CanCollide = false
|
|
82
|
+
partClone.CanQuery = false
|
|
83
|
+
partClone.CanTouch = false
|
|
84
|
+
partClone.Transparency = 1
|
|
85
|
+
partClone.Size = Vector3.one
|
|
86
|
+
partClone.CFrame = parent.CFrame
|
|
87
|
+
partClone.Parent = soundWidget
|
|
88
|
+
|
|
89
|
+
clone.Parent = partClone
|
|
90
|
+
|
|
91
|
+
table.insert(scope, partClone)
|
|
92
|
+
else
|
|
93
|
+
clone.Parent = soundWidget
|
|
94
|
+
table.insert(scope, clone)
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
if parent then
|
|
98
|
+
clone.Parent = parent
|
|
99
|
+
else
|
|
100
|
+
clone.Parent = workspace.Terrain
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
table.insert(scope, clone)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
return clone, partClone
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
local function playSingleSound(
|
|
110
|
+
ref: Sound,
|
|
111
|
+
src: Sound,
|
|
112
|
+
clone: Sound,
|
|
113
|
+
partClone: Part?,
|
|
114
|
+
scope: types.scope,
|
|
115
|
+
emitDuration: number
|
|
116
|
+
): RBXScriptConnection?
|
|
117
|
+
local currentSpeed = clone.PlaybackSpeed
|
|
118
|
+
|
|
119
|
+
local volumeEnd = attr.get(src, "Volume_End", clone.Volume)
|
|
120
|
+
local volumeStart = attr.get(src, "Volume_Start", clone.Volume)
|
|
121
|
+
|
|
122
|
+
clone.Volume = volumeStart
|
|
123
|
+
|
|
124
|
+
if volumeStart ~= volumeEnd then
|
|
125
|
+
local volumeDuration = attr.get(src, "Volume_Duration", clone.TimeLength)
|
|
126
|
+
|
|
127
|
+
table.insert(
|
|
128
|
+
scope,
|
|
129
|
+
tween.fromParams(attr.get(src, "Volume_Curve", utility.default_bezier), volumeDuration, function(alpha, deltaTime)
|
|
130
|
+
clone.Volume = utility.lerp(volumeStart, volumeEnd, alpha)
|
|
131
|
+
return deltaTime * currentSpeed
|
|
132
|
+
end)
|
|
133
|
+
)
|
|
134
|
+
else
|
|
135
|
+
clone.Volume = volumeStart
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
local speedEnd = attr.get(src, "Speed_End", clone.PlaybackSpeed)
|
|
139
|
+
local speedStart = attr.get(src, "Speed_Start", clone.PlaybackSpeed)
|
|
140
|
+
|
|
141
|
+
currentSpeed = speedStart
|
|
142
|
+
|
|
143
|
+
local speedTween: RBXScriptConnection? = nil
|
|
144
|
+
|
|
145
|
+
if speedStart ~= speedEnd then
|
|
146
|
+
local speedDuration = attr.get(src, "Speed_Duration", clone.TimeLength)
|
|
147
|
+
|
|
148
|
+
speedTween = tween.fromParams(
|
|
149
|
+
attr.get(src, "Speed_Curve", utility.default_bezier),
|
|
150
|
+
speedDuration,
|
|
151
|
+
function(alpha, deltaTime)
|
|
152
|
+
currentSpeed = utility.lerp(speedStart, speedEnd, alpha)
|
|
153
|
+
clone.PlaybackSpeed = currentSpeed
|
|
154
|
+
return deltaTime
|
|
155
|
+
end
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
table.insert(scope, speedTween)
|
|
159
|
+
else
|
|
160
|
+
clone.PlaybackSpeed = speedStart
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
local rolloffEnd = attr.get(src, "RollOff_End", clone.RollOffMinDistance)
|
|
164
|
+
local rolloffStart = attr.get(src, "RollOff_Start", clone.RollOffMinDistance)
|
|
165
|
+
|
|
166
|
+
if rolloffStart ~= rolloffEnd then
|
|
167
|
+
local rolloffDuration = attr.get(src, "RollOff_Duration", clone.TimeLength)
|
|
168
|
+
|
|
169
|
+
table.insert(
|
|
170
|
+
scope,
|
|
171
|
+
tween.fromParams(
|
|
172
|
+
attr.get(src, "RollOff_Curve", utility.default_bezier),
|
|
173
|
+
rolloffDuration,
|
|
174
|
+
function(alpha, deltaTime)
|
|
175
|
+
clone.RollOffMinDistance = utility.lerp(rolloffStart, rolloffEnd, alpha)
|
|
176
|
+
return deltaTime * currentSpeed
|
|
177
|
+
end,
|
|
178
|
+
speedTween
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
if clone.PlayOnRemove and not utility.PLUGIN_CONTEXT then
|
|
184
|
+
clone:Destroy()
|
|
185
|
+
else
|
|
186
|
+
clone:Play()
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
if partClone then
|
|
190
|
+
local parent = ref:FindFirstAncestorWhichIsA("BasePart")
|
|
191
|
+
|
|
192
|
+
if parent then
|
|
193
|
+
table.insert(
|
|
194
|
+
scope,
|
|
195
|
+
RunService.Heartbeat:Connect(function()
|
|
196
|
+
if partClone.Parent then
|
|
197
|
+
partClone.CFrame = parent.CFrame
|
|
198
|
+
end
|
|
199
|
+
end)
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
if emitDuration > 0 then
|
|
205
|
+
task.wait(emitDuration)
|
|
206
|
+
clone:Stop()
|
|
207
|
+
else
|
|
208
|
+
tween.timer(clone.TimeLength, function(deltaTime, elapsed)
|
|
209
|
+
return if clone.PlaybackSpeed > 0 or (elapsed > 0 and speedTween and speedTween.Connected)
|
|
210
|
+
then deltaTime * clone.PlaybackSpeed
|
|
211
|
+
else nil
|
|
212
|
+
end, speedTween, scope)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
return speedTween
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
function sound.init()
|
|
219
|
+
if not utility.PLUGIN_CONTEXT then
|
|
220
|
+
return
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
local plugin = script:FindFirstAncestorOfClass("Plugin")
|
|
224
|
+
|
|
225
|
+
if not plugin then
|
|
226
|
+
return
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
soundWidget = plugin:CreateDockWidgetPluginGui(
|
|
230
|
+
"VFXForgeSoundPlayer",
|
|
231
|
+
DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Float, false, true, 200, 100, 100, 50)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if soundWidget then
|
|
235
|
+
soundWidget.Name = "VFXForgeSoundPlayer"
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
function sound.deinit()
|
|
240
|
+
if soundWidget then
|
|
241
|
+
soundWidget:Destroy()
|
|
242
|
+
soundWidget = nil
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
function sound.emit(ref: Sound, scope: types.scope)
|
|
247
|
+
if ref.Playing then
|
|
248
|
+
ref:Stop()
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
local isSource = attr.get(ref, "SoundPoolIsSource", false)
|
|
252
|
+
local inheritPoolAttributes = attr.get(ref, "SoundPoolInheritAttributes", false)
|
|
253
|
+
|
|
254
|
+
if inheritPoolAttributes then
|
|
255
|
+
ref = not isSource and selectRandomSound(ref) or ref
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
local emitDelay = attr.get(ref, "EmitDelay", 0)
|
|
259
|
+
local emitDuration = attr.get(ref, "EmitDuration", 0)
|
|
260
|
+
|
|
261
|
+
local emitCount = attr.get(ref, "EmitCount", 1)
|
|
262
|
+
local emitInterval = attr.getRange(ref, "EmitInterval", NumberRange.new(0, 0))
|
|
263
|
+
|
|
264
|
+
local repeatCount = attr.get(ref, "RepeatCount", 1)
|
|
265
|
+
local repeatInterval = attr.getRange(ref, "RepeatInterval", NumberRange.new(0, 0))
|
|
266
|
+
|
|
267
|
+
task.wait(emitDelay)
|
|
268
|
+
|
|
269
|
+
local promises = {}
|
|
270
|
+
|
|
271
|
+
for i = 1, emitCount do
|
|
272
|
+
local sourceSound = inheritPoolAttributes and ref or (not isSource and selectRandomSound(ref) or ref)
|
|
273
|
+
|
|
274
|
+
if not sourceSound then
|
|
275
|
+
continue
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
local src = if inheritPoolAttributes and sourceSound ~= ref then sourceSound else ref
|
|
279
|
+
|
|
280
|
+
for r = 1, repeatCount do
|
|
281
|
+
table.insert(
|
|
282
|
+
promises,
|
|
283
|
+
Promise.new(function(resolve)
|
|
284
|
+
local clone, partClone = createSoundClone(ref, sourceSound, scope)
|
|
285
|
+
|
|
286
|
+
playSingleSound(ref, src, clone, partClone, scope, emitDuration)
|
|
287
|
+
|
|
288
|
+
if partClone then
|
|
289
|
+
partClone:Destroy()
|
|
290
|
+
else
|
|
291
|
+
clone:Destroy()
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
resolve()
|
|
295
|
+
end)
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
if r < repeatCount and repeatInterval.Max > 0 then
|
|
299
|
+
task.wait(rng:NextNumber(repeatInterval.Min, repeatInterval.Max))
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
if i < emitCount and emitInterval.Max > 0 then
|
|
304
|
+
task.wait(rng:NextNumber(emitInterval.Min, emitInterval.Max))
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
Promise.all(promises):await()
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
return sound
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
local attr = require("@mod/attributes")
|
|
2
|
+
local tween = require("@mod/tween")
|
|
3
|
+
local types = require("@root/types")
|
|
4
|
+
local utility = require("@mod/utility")
|
|
5
|
+
|
|
6
|
+
local spin = {}
|
|
7
|
+
|
|
8
|
+
function spin.emit(ref: Model, scope: types.scope)
|
|
9
|
+
if utility.isSpinModelStatic(ref) then
|
|
10
|
+
return
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
local rotation = attr.get(ref, "SpinRotation", vector.zero) * utility.DEG_TO_RAD
|
|
14
|
+
|
|
15
|
+
local scaleStart = attr.get(ref, "Scale_Start", 1)
|
|
16
|
+
local scaleEnd = attr.get(ref, "Scale_End", 1)
|
|
17
|
+
|
|
18
|
+
local emitDelay = attr.get(ref, "EmitDelay", 0)
|
|
19
|
+
local resetDelay = attr.get(ref, "ResetDelay", 0)
|
|
20
|
+
local resetOnFinish = attr.get(ref, "ResetOnFinish", true)
|
|
21
|
+
|
|
22
|
+
local sync = attr.get(ref, "SyncPosition", false)
|
|
23
|
+
local duration = attr.get(ref, "SpinDuration", 0.5)
|
|
24
|
+
|
|
25
|
+
local speedDuration = attr.get(ref, "SpinSpeed_Duration", 0.1)
|
|
26
|
+
|
|
27
|
+
local speedStart = attr.get(ref, "SpinSpeed_Start", 0)
|
|
28
|
+
local speedEnd = attr.get(ref, "SpinSpeed_End", 1)
|
|
29
|
+
|
|
30
|
+
local currentSpeed = speedStart
|
|
31
|
+
|
|
32
|
+
task.wait(emitDelay)
|
|
33
|
+
|
|
34
|
+
local speedTween
|
|
35
|
+
|
|
36
|
+
if speedStart ~= speedEnd then
|
|
37
|
+
speedTween = tween.fromParams(
|
|
38
|
+
attr.get(ref, "SpinSpeed_Curve", utility.default_bezier),
|
|
39
|
+
speedDuration,
|
|
40
|
+
function(alpha, deltaTime)
|
|
41
|
+
currentSpeed = utility.lerp(speedStart, speedEnd, alpha)
|
|
42
|
+
return deltaTime
|
|
43
|
+
end
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
table.insert(scope, speedTween)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
local originalPivot = ref:GetPivot()
|
|
50
|
+
local originalScale = ref:GetScale()
|
|
51
|
+
|
|
52
|
+
if scaleStart ~= scaleEnd then
|
|
53
|
+
table.insert(
|
|
54
|
+
scope,
|
|
55
|
+
tween.fromParams(attr.get(ref, "Scale_Curve", utility.default_bezier), duration, function(alpha, deltaTime)
|
|
56
|
+
ref:ScaleTo(utility.lerp(scaleStart, scaleEnd, alpha))
|
|
57
|
+
return deltaTime
|
|
58
|
+
end)
|
|
59
|
+
)
|
|
60
|
+
else
|
|
61
|
+
ref:ScaleTo(scaleStart)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
local parent = ref:FindFirstAncestorOfClass("Attachment") or ref:FindFirstAncestorWhichIsA("BasePart")
|
|
65
|
+
|
|
66
|
+
if resetOnFinish then
|
|
67
|
+
table.insert(scope, function()
|
|
68
|
+
ref:PivotTo(parent and utility.getTransformedOriginExtents(parent) or originalPivot)
|
|
69
|
+
ref:ScaleTo(originalScale)
|
|
70
|
+
end)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
rotation *= duration
|
|
74
|
+
|
|
75
|
+
tween.timer(duration + resetDelay, function(deltaTime, elapsed)
|
|
76
|
+
local alpha = math.clamp(elapsed / duration, 0, 1)
|
|
77
|
+
|
|
78
|
+
local r = rotation * alpha
|
|
79
|
+
local cf = (if parent and sync then utility.getTransformedOriginExtents(parent) else originalPivot)
|
|
80
|
+
* CFrame.fromOrientation(r.x, r.y, r.z)
|
|
81
|
+
|
|
82
|
+
ref:PivotTo(cf)
|
|
83
|
+
|
|
84
|
+
return if currentSpeed > 0 or (elapsed > 0 and speedTween.Connected) then deltaTime * currentSpeed else nil
|
|
85
|
+
end, speedTween, scope, utility.RENDER_PRIORITY + scope.depth)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return spin
|