azul-sync 1.3.1 → 1.3.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/dist/push.d.ts +2 -0
- package/dist/push.d.ts.map +1 -1
- package/dist/push.js +46 -1
- package/dist/push.js.map +1 -1
- package/package.json +1 -1
- package/plugin/README.md +0 -54
- package/plugin/sourcemap.json +0 -272
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/Actor/AzulSync.server.luau +0 -906
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/AzulService.luau +0 -573
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/Config.luau +0 -31
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/Enums.luau +0 -11
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/Serializer.luau +0 -929
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/CollapsibleTitledSection.luau +0 -214
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/ColorPicker.luau +0 -360
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/CustomTextButton.luau +0 -170
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/DropdownMenu.luau +0 -363
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/HorizontalLine.luau +0 -43
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/ImageButtonWithText.luau +0 -181
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledCheckbox.luau +0 -295
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledColorInputPicker.luau +0 -294
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledMultiChoice.luau +0 -163
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledNumberInput.luau +0 -312
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledRadioButton.luau +0 -55
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledSlider.luau +0 -151
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledTextInput.luau +0 -222
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledToggleButton.luau +0 -73
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/StatefulImageButton.luau +0 -125
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/VerticalScrollingFrame.luau +0 -100
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/VerticalSpacer.luau +0 -35
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/VerticallyScalingListFrame.luau +0 -107
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/GuiUtilities.luau +0 -429
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/RbxGui.luau +0 -4363
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/UI.luau +0 -477
- package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/WebSocketClient.luau +0 -161
|
@@ -1,929 +0,0 @@
|
|
|
1
|
-
local Serializer = {}
|
|
2
|
-
|
|
3
|
-
local ReflectionService = game:GetService("ReflectionService")
|
|
4
|
-
local CollectionService = game:GetService("CollectionService")
|
|
5
|
-
|
|
6
|
-
local FALLBACK_SERIALIZABLE_PROPERTIES = {
|
|
7
|
-
"Archivable",
|
|
8
|
-
"Enabled",
|
|
9
|
-
"RunContext",
|
|
10
|
-
"LinkedSource",
|
|
11
|
-
"Disabled",
|
|
12
|
-
"Visible",
|
|
13
|
-
"Active",
|
|
14
|
-
"AutomaticSize",
|
|
15
|
-
"AnchorPoint",
|
|
16
|
-
"Position",
|
|
17
|
-
"Rotation",
|
|
18
|
-
"Size",
|
|
19
|
-
"Text",
|
|
20
|
-
"TextColor3",
|
|
21
|
-
"TextSize",
|
|
22
|
-
"BackgroundColor3",
|
|
23
|
-
"BackgroundTransparency",
|
|
24
|
-
"Image",
|
|
25
|
-
"ImageColor3",
|
|
26
|
-
"ImageTransparency",
|
|
27
|
-
"LayoutOrder",
|
|
28
|
-
"ZIndex",
|
|
29
|
-
"AutoButtonColor",
|
|
30
|
-
"RichText",
|
|
31
|
-
"TextScaled",
|
|
32
|
-
"TextWrapped",
|
|
33
|
-
"FontFace",
|
|
34
|
-
"Transparency",
|
|
35
|
-
"Color",
|
|
36
|
-
"Material",
|
|
37
|
-
"Reflectance",
|
|
38
|
-
"CanCollide",
|
|
39
|
-
"CanTouch",
|
|
40
|
-
"CanQuery",
|
|
41
|
-
"CastShadow",
|
|
42
|
-
"Locked",
|
|
43
|
-
"Massless",
|
|
44
|
-
"Shape",
|
|
45
|
-
"SizeConstraint",
|
|
46
|
-
"CFrame",
|
|
47
|
-
"Orientation",
|
|
48
|
-
"PivotOffset",
|
|
49
|
-
"BrickColor",
|
|
50
|
-
"TopSurface",
|
|
51
|
-
"BottomSurface",
|
|
52
|
-
"LeftSurface",
|
|
53
|
-
"RightSurface",
|
|
54
|
-
"FrontSurface",
|
|
55
|
-
"BackSurface",
|
|
56
|
-
"MeshId",
|
|
57
|
-
"TextureID",
|
|
58
|
-
"DoubleSided",
|
|
59
|
-
"Offset",
|
|
60
|
-
"Scale",
|
|
61
|
-
"SoundId",
|
|
62
|
-
"Volume",
|
|
63
|
-
"PlaybackSpeed",
|
|
64
|
-
"Looped",
|
|
65
|
-
"RollOffMode",
|
|
66
|
-
"RollOffMaxDistance",
|
|
67
|
-
"RollOffMinDistance",
|
|
68
|
-
"PlayOnRemove",
|
|
69
|
-
"AnimationId",
|
|
70
|
-
"Value",
|
|
71
|
-
"CurrentCamera",
|
|
72
|
-
"FieldOfView",
|
|
73
|
-
"Ambient",
|
|
74
|
-
"Brightness",
|
|
75
|
-
"ClockTime",
|
|
76
|
-
"FogColor",
|
|
77
|
-
"FogEnd",
|
|
78
|
-
"FogStart",
|
|
79
|
-
"GlobalShadows",
|
|
80
|
-
"OutdoorAmbient",
|
|
81
|
-
"Technology",
|
|
82
|
-
"PrimaryPart",
|
|
83
|
-
"WorldPivot",
|
|
84
|
-
"MetalnessMap",
|
|
85
|
-
"NormalMap",
|
|
86
|
-
"RoughnessMap",
|
|
87
|
-
"ColorMap",
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
local FALLBACK_SERIALIZABLE_PROPERTY_SET: { [string]: boolean } = {}
|
|
91
|
-
for _, propertyName in ipairs(FALLBACK_SERIALIZABLE_PROPERTIES) do
|
|
92
|
-
FALLBACK_SERIALIZABLE_PROPERTY_SET[propertyName] = true
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
local SERIALIZABLE_PROPERTIES_CACHE: { string }? = nil
|
|
96
|
-
local SERIALIZABLE_PROPERTY_SET_CACHE: { [string]: boolean }? = nil
|
|
97
|
-
local writablePropertiesByClass: { [string]: { [string]: boolean } } = {}
|
|
98
|
-
local defaultSerializedPropertiesByClass: { [string]: { [string]: any } } = {}
|
|
99
|
-
local canonicalPropertyByLowerByClass: { [string]: { [string]: string } } = {}
|
|
100
|
-
|
|
101
|
-
local function enumItemFromNumber(enumItems: { any }, value: number): any
|
|
102
|
-
for _, item in ipairs(enumItems) do
|
|
103
|
-
if item.Value == value then return item end
|
|
104
|
-
end
|
|
105
|
-
return nil
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
local function enumItemFromName(enumItems: { any }, name: string): any
|
|
109
|
-
for _, item in ipairs(enumItems) do
|
|
110
|
-
if item.Name == name then return item end
|
|
111
|
-
end
|
|
112
|
-
return nil
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
local function parseCFrameFromValue(recordValue: any): CFrame?
|
|
116
|
-
if type(recordValue) ~= "table" then return nil end
|
|
117
|
-
|
|
118
|
-
if type(recordValue.position) == "table" and type(recordValue.rotation) == "table" then
|
|
119
|
-
local position = recordValue.position
|
|
120
|
-
local rotation = recordValue.rotation
|
|
121
|
-
if #position == 3 and #rotation == 9 then
|
|
122
|
-
return CFrame.new(
|
|
123
|
-
position[1],
|
|
124
|
-
position[2],
|
|
125
|
-
position[3],
|
|
126
|
-
rotation[1],
|
|
127
|
-
rotation[2],
|
|
128
|
-
rotation[3],
|
|
129
|
-
rotation[4],
|
|
130
|
-
rotation[5],
|
|
131
|
-
rotation[6],
|
|
132
|
-
rotation[7],
|
|
133
|
-
rotation[8],
|
|
134
|
-
rotation[9]
|
|
135
|
-
)
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
if type(recordValue.components) == "table" then return CFrame.new(unpack(recordValue.components)) end
|
|
140
|
-
|
|
141
|
-
return nil
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
local function getInstancePathSegments(instance: Instance): { string }?
|
|
145
|
-
local path = {}
|
|
146
|
-
local current: Instance? = instance
|
|
147
|
-
|
|
148
|
-
while current and current ~= game do
|
|
149
|
-
table.insert(path, 1, current.Name)
|
|
150
|
-
current = current.Parent
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
if current ~= game then return nil end
|
|
154
|
-
return path
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
local function findInstanceByPath(pathSegments: { string }): Instance?
|
|
158
|
-
local current: Instance = game
|
|
159
|
-
for index, segment in ipairs(pathSegments) do
|
|
160
|
-
local nextNode: Instance? = nil
|
|
161
|
-
|
|
162
|
-
if index == 1 then
|
|
163
|
-
local ok, service = pcall(function()
|
|
164
|
-
return game:GetService(segment)
|
|
165
|
-
end)
|
|
166
|
-
if ok and service then nextNode = service end
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
if not nextNode then nextNode = current:FindFirstChild(segment) end
|
|
170
|
-
if not nextNode then return nil end
|
|
171
|
-
current = nextNode
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
return current
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
function Serializer.serializeValue(
|
|
178
|
-
value: any,
|
|
179
|
-
options: { getInstanceRefData: ((Instance) -> { guid: string?, path: { string }? }?)? }?
|
|
180
|
-
): any
|
|
181
|
-
local kind = typeof(value)
|
|
182
|
-
|
|
183
|
-
if kind == "nil" then return nil end
|
|
184
|
-
if kind == "boolean" or kind == "string" or kind == "number" then return value end
|
|
185
|
-
|
|
186
|
-
if kind == "Color3" then
|
|
187
|
-
local color = value :: Color3
|
|
188
|
-
return { __type = "Color3", r = color.R, g = color.G, b = color.B }
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
if kind == "Vector2" then
|
|
192
|
-
local vector = value :: Vector2
|
|
193
|
-
return { __type = "Vector2", x = vector.X, y = vector.Y }
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
if kind == "Vector2int16" then
|
|
197
|
-
local vector = value :: Vector2int16
|
|
198
|
-
return { __type = "Vector2int16", x = vector.X, y = vector.Y }
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
if kind == "Vector3" then
|
|
202
|
-
local vector = value :: Vector3
|
|
203
|
-
return { __type = "Vector3", x = vector.X, y = vector.Y, z = vector.Z }
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
if kind == "Vector3int16" then
|
|
207
|
-
local vector = value :: Vector3int16
|
|
208
|
-
return { __type = "Vector3int16", x = vector.X, y = vector.Y, z = vector.Z }
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
if kind == "UDim" then
|
|
212
|
-
local u = value :: UDim
|
|
213
|
-
return { __type = "UDim", scale = u.Scale, offset = u.Offset }
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
if kind == "UDim2" then
|
|
217
|
-
local u = value :: UDim2
|
|
218
|
-
return {
|
|
219
|
-
__type = "UDim2",
|
|
220
|
-
xScale = u.X.Scale,
|
|
221
|
-
xOffset = u.X.Offset,
|
|
222
|
-
yScale = u.Y.Scale,
|
|
223
|
-
yOffset = u.Y.Offset,
|
|
224
|
-
}
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
if kind == "Rect" then
|
|
228
|
-
local rect = value :: Rect
|
|
229
|
-
return {
|
|
230
|
-
__type = "Rect",
|
|
231
|
-
minX = rect.Min.X,
|
|
232
|
-
minY = rect.Min.Y,
|
|
233
|
-
maxX = rect.Max.X,
|
|
234
|
-
maxY = rect.Max.Y,
|
|
235
|
-
}
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
if kind == "CFrame" then
|
|
239
|
-
local cf = value :: CFrame
|
|
240
|
-
return { __type = "CFrame", components = { cf:GetComponents() } }
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
if kind == "BrickColor" then
|
|
244
|
-
local brick = value :: BrickColor
|
|
245
|
-
return { __type = "BrickColor", number = brick.Number }
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
if kind == "EnumItem" then
|
|
249
|
-
local enumItem = value :: EnumItem
|
|
250
|
-
return {
|
|
251
|
-
__type = "EnumItem",
|
|
252
|
-
enumType = tostring(enumItem.EnumType),
|
|
253
|
-
name = enumItem.Name,
|
|
254
|
-
}
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
if kind == "NumberRange" then
|
|
258
|
-
local range = value :: NumberRange
|
|
259
|
-
return { __type = "NumberRange", min = range.Min, max = range.Max }
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
if kind == "NumberSequence" then
|
|
263
|
-
local sequence = value :: NumberSequence
|
|
264
|
-
local keypoints = {}
|
|
265
|
-
for _, keypoint in ipairs(sequence.Keypoints) do
|
|
266
|
-
table.insert(keypoints, {
|
|
267
|
-
time = keypoint.Time,
|
|
268
|
-
value = keypoint.Value,
|
|
269
|
-
envelope = keypoint.Envelope,
|
|
270
|
-
})
|
|
271
|
-
end
|
|
272
|
-
return { __type = "NumberSequence", keypoints = keypoints }
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
if kind == "ColorSequence" then
|
|
276
|
-
local sequence = value :: ColorSequence
|
|
277
|
-
local keypoints = {}
|
|
278
|
-
for _, keypoint in ipairs(sequence.Keypoints) do
|
|
279
|
-
table.insert(keypoints, {
|
|
280
|
-
time = keypoint.Time,
|
|
281
|
-
color = Serializer.serializeValue(keypoint.Value, options),
|
|
282
|
-
})
|
|
283
|
-
end
|
|
284
|
-
return { __type = "ColorSequence", keypoints = keypoints }
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
if kind == "Font" then
|
|
288
|
-
local font = value :: Font
|
|
289
|
-
return {
|
|
290
|
-
__type = "Font",
|
|
291
|
-
family = font.Family,
|
|
292
|
-
weight = font.Weight and font.Weight.Value or nil,
|
|
293
|
-
style = font.Style and font.Style.Value or nil,
|
|
294
|
-
}
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
if kind == "PhysicalProperties" then
|
|
298
|
-
local props = value :: PhysicalProperties
|
|
299
|
-
return {
|
|
300
|
-
__type = "PhysicalProperties",
|
|
301
|
-
density = props.Density,
|
|
302
|
-
friction = props.Friction,
|
|
303
|
-
elasticity = props.Elasticity,
|
|
304
|
-
frictionWeight = props.FrictionWeight,
|
|
305
|
-
elasticityWeight = props.ElasticityWeight,
|
|
306
|
-
}
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
if kind == "Axes" then
|
|
310
|
-
local axes = value :: Axes
|
|
311
|
-
return { __type = "Axes", x = axes.X, y = axes.Y, z = axes.Z }
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
if kind == "Faces" then
|
|
315
|
-
local faces = value :: Faces
|
|
316
|
-
return {
|
|
317
|
-
__type = "Faces",
|
|
318
|
-
top = faces.Top,
|
|
319
|
-
bottom = faces.Bottom,
|
|
320
|
-
left = faces.Left,
|
|
321
|
-
right = faces.Right,
|
|
322
|
-
front = faces.Front,
|
|
323
|
-
back = faces.Back,
|
|
324
|
-
}
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
if kind == "Ray" then
|
|
328
|
-
local ray = value :: Ray
|
|
329
|
-
return {
|
|
330
|
-
__type = "Ray",
|
|
331
|
-
origin = Serializer.serializeValue(ray.Origin, options),
|
|
332
|
-
direction = Serializer.serializeValue(ray.Direction, options),
|
|
333
|
-
}
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
if kind == "Instance" then
|
|
337
|
-
local instanceValue = value :: Instance
|
|
338
|
-
local guid: string? = nil
|
|
339
|
-
local path: { string }? = nil
|
|
340
|
-
|
|
341
|
-
if options and options.getInstanceRefData then
|
|
342
|
-
local ok, refData = pcall(function()
|
|
343
|
-
return options.getInstanceRefData(instanceValue)
|
|
344
|
-
end)
|
|
345
|
-
if ok and type(refData) == "table" then
|
|
346
|
-
if type(refData.guid) == "string" then guid = refData.guid end
|
|
347
|
-
if type(refData.path) == "table" then path = refData.path end
|
|
348
|
-
end
|
|
349
|
-
end
|
|
350
|
-
|
|
351
|
-
if not guid then
|
|
352
|
-
local okGuid, debugId = pcall(function()
|
|
353
|
-
return instanceValue:GetDebugId(0)
|
|
354
|
-
end)
|
|
355
|
-
if okGuid and type(debugId) == "string" and debugId ~= "" then guid = debugId end
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
if not path then path = getInstancePathSegments(instanceValue) end
|
|
359
|
-
|
|
360
|
-
return {
|
|
361
|
-
__type = "InstanceRef",
|
|
362
|
-
guid = guid,
|
|
363
|
-
path = path,
|
|
364
|
-
}
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
if kind == "Region3" then
|
|
368
|
-
local cframe = value.CFrame
|
|
369
|
-
local size = value.Size
|
|
370
|
-
return {
|
|
371
|
-
__type = "Region3",
|
|
372
|
-
min = { x = cframe.X - size.X / 2, y = cframe.Y - size.Y / 2, z = cframe.Z - size.Z / 2 },
|
|
373
|
-
max = { x = cframe.X + size.X / 2, y = cframe.Y + size.Y / 2, z = cframe.Z + size.Z / 2 },
|
|
374
|
-
}
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
if kind == "Region3int16" then
|
|
378
|
-
local region = value :: Region3int16
|
|
379
|
-
return {
|
|
380
|
-
__type = "Region3int16",
|
|
381
|
-
min = { x = region.Min.X, y = region.Min.Y, z = region.Min.Z },
|
|
382
|
-
max = { x = region.Max.X, y = region.Max.Y, z = region.Max.Z },
|
|
383
|
-
}
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
return nil
|
|
387
|
-
end
|
|
388
|
-
|
|
389
|
-
function Serializer.deserializeValue(
|
|
390
|
-
value: any,
|
|
391
|
-
options: {
|
|
392
|
-
resolveInstanceByGuid: ((string) -> Instance?)?,
|
|
393
|
-
resolveInstanceByPath: (({ string }) -> Instance?)?,
|
|
394
|
-
}?
|
|
395
|
-
): any
|
|
396
|
-
if type(value) ~= "table" then return value end
|
|
397
|
-
|
|
398
|
-
local record = value :: { [string]: any }
|
|
399
|
-
local valueType = record.__type
|
|
400
|
-
local payload = record
|
|
401
|
-
|
|
402
|
-
if type(valueType) ~= "string" then
|
|
403
|
-
if type(record.type) ~= "string" then return value end
|
|
404
|
-
valueType = record.type
|
|
405
|
-
payload = record.value
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
if valueType == "Ref" then
|
|
409
|
-
local guidFromRef = if type(payload) == "string" then payload else nil
|
|
410
|
-
valueType = "InstanceRef"
|
|
411
|
-
payload = {
|
|
412
|
-
guid = guidFromRef,
|
|
413
|
-
path = nil,
|
|
414
|
-
}
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
if valueType == "nil" then return nil end
|
|
418
|
-
if valueType == "Color3" then
|
|
419
|
-
local source = if type(payload) == "table" then payload else record
|
|
420
|
-
return Color3.new(source.r, source.g, source.b)
|
|
421
|
-
end
|
|
422
|
-
if valueType == "Color3uint8" then
|
|
423
|
-
local source = if type(payload) == "table" then payload else record
|
|
424
|
-
return Color3.fromRGB(source.r, source.g, source.b)
|
|
425
|
-
end
|
|
426
|
-
if valueType == "Vector2" then
|
|
427
|
-
local source = if type(payload) == "table" then payload else record
|
|
428
|
-
return Vector2.new(source.x, source.y)
|
|
429
|
-
end
|
|
430
|
-
if valueType == "Vector2int16" then
|
|
431
|
-
local source = if type(payload) == "table" then payload else record
|
|
432
|
-
return Vector2int16.new(source.x, source.y)
|
|
433
|
-
end
|
|
434
|
-
if valueType == "Vector3" then
|
|
435
|
-
local source = if type(payload) == "table" then payload else record
|
|
436
|
-
return Vector3.new(source.x, source.y, source.z)
|
|
437
|
-
end
|
|
438
|
-
if valueType == "Vector3int16" then
|
|
439
|
-
local source = if type(payload) == "table" then payload else record
|
|
440
|
-
return Vector3int16.new(source.x, source.y, source.z)
|
|
441
|
-
end
|
|
442
|
-
if valueType == "UDim" then
|
|
443
|
-
local source = if type(payload) == "table" then payload else record
|
|
444
|
-
return UDim.new(source.scale, source.offset)
|
|
445
|
-
end
|
|
446
|
-
if valueType == "UDim2" then
|
|
447
|
-
if type(payload) == "table" and type(payload.x) == "table" and type(payload.y) == "table" then
|
|
448
|
-
return UDim2.new(payload.x.scale, payload.x.offset, payload.y.scale, payload.y.offset)
|
|
449
|
-
end
|
|
450
|
-
return UDim2.new(record.xScale, record.xOffset, record.yScale, record.yOffset)
|
|
451
|
-
end
|
|
452
|
-
if valueType == "Rect" then
|
|
453
|
-
if type(payload) == "table" and type(payload.min) == "table" and type(payload.max) == "table" then
|
|
454
|
-
return Rect.new(payload.min.x, payload.min.y, payload.max.x, payload.max.y)
|
|
455
|
-
end
|
|
456
|
-
return Rect.new(record.minX, record.minY, record.maxX, record.maxY)
|
|
457
|
-
end
|
|
458
|
-
if valueType == "CFrame" then
|
|
459
|
-
if type(record.components) == "table" then return CFrame.new(unpack(record.components)) end
|
|
460
|
-
return parseCFrameFromValue(payload)
|
|
461
|
-
end
|
|
462
|
-
if valueType == "OptionalCFrame" or valueType == "OptionalCoordinateFrame" then
|
|
463
|
-
if payload == nil then return nil end
|
|
464
|
-
return parseCFrameFromValue(payload)
|
|
465
|
-
end
|
|
466
|
-
if valueType == "BrickColor" then
|
|
467
|
-
local source = if type(payload) == "number" then payload else record.number
|
|
468
|
-
return BrickColor.new(source)
|
|
469
|
-
end
|
|
470
|
-
if valueType == "EnumItem" then
|
|
471
|
-
if type(record.enumType) ~= "string" or type(record.name) ~= "string" then return nil end
|
|
472
|
-
local enumName = record.enumType:match("Enum%.(.+)") or record.enumType
|
|
473
|
-
local enum = Enum[enumName]
|
|
474
|
-
if enum then return enum[record.name] end
|
|
475
|
-
return nil
|
|
476
|
-
end
|
|
477
|
-
if valueType == "Enum" then
|
|
478
|
-
if type(payload) ~= "table" then return nil end
|
|
479
|
-
local enumName = payload.enumType
|
|
480
|
-
if type(enumName) ~= "string" then return nil end
|
|
481
|
-
local enum = Enum[enumName]
|
|
482
|
-
if not enum then return nil end
|
|
483
|
-
if type(payload.value) == "string" then return enum[payload.value] end
|
|
484
|
-
if type(payload.value) == "number" then return enumItemFromNumber(enum:GetEnumItems(), payload.value) end
|
|
485
|
-
return nil
|
|
486
|
-
end
|
|
487
|
-
if valueType == "NumberRange" then
|
|
488
|
-
if type(payload) == "table" then return NumberRange.new(payload.min, payload.max) end
|
|
489
|
-
return NumberRange.new(record.min, record.max)
|
|
490
|
-
end
|
|
491
|
-
if valueType == "NumberSequence" then
|
|
492
|
-
local source = record.keypoints
|
|
493
|
-
if type(payload) == "table" and type(payload.keypoints) == "table" then source = payload.keypoints end
|
|
494
|
-
local keypoints = {}
|
|
495
|
-
for _, keypoint in ipairs(source or {}) do
|
|
496
|
-
table.insert(keypoints, NumberSequenceKeypoint.new(keypoint.time, keypoint.value, keypoint.envelope or 0))
|
|
497
|
-
end
|
|
498
|
-
if #keypoints > 0 then return NumberSequence.new(keypoints) end
|
|
499
|
-
return nil
|
|
500
|
-
end
|
|
501
|
-
if valueType == "ColorSequence" then
|
|
502
|
-
local source = record.keypoints
|
|
503
|
-
if type(payload) == "table" and type(payload.keypoints) == "table" then source = payload.keypoints end
|
|
504
|
-
local keypoints = {}
|
|
505
|
-
for _, keypoint in ipairs(source or {}) do
|
|
506
|
-
local color = keypoint.color
|
|
507
|
-
if type(color) == "table" and color.r ~= nil and color.g ~= nil and color.b ~= nil then
|
|
508
|
-
color = Color3.new(color.r, color.g, color.b)
|
|
509
|
-
else
|
|
510
|
-
color = Serializer.deserializeValue(color, options)
|
|
511
|
-
end
|
|
512
|
-
|
|
513
|
-
if typeof(color) == "Color3" then
|
|
514
|
-
table.insert(keypoints, ColorSequenceKeypoint.new(keypoint.time, color))
|
|
515
|
-
end
|
|
516
|
-
end
|
|
517
|
-
if #keypoints > 0 then return ColorSequence.new(keypoints) end
|
|
518
|
-
return nil
|
|
519
|
-
end
|
|
520
|
-
if valueType == "Font" then
|
|
521
|
-
local source = if type(payload) == "table" then payload else record
|
|
522
|
-
local weight = Enum.FontWeight.Regular
|
|
523
|
-
local style = Enum.FontStyle.Normal
|
|
524
|
-
|
|
525
|
-
if type(source.weight) == "number" then
|
|
526
|
-
local matched = enumItemFromNumber(Enum.FontWeight:GetEnumItems(), source.weight)
|
|
527
|
-
if matched then weight = matched end
|
|
528
|
-
elseif type(source.weight) == "string" then
|
|
529
|
-
local matched = enumItemFromName(Enum.FontWeight:GetEnumItems(), source.weight)
|
|
530
|
-
if matched then weight = matched end
|
|
531
|
-
end
|
|
532
|
-
|
|
533
|
-
if type(source.style) == "number" then
|
|
534
|
-
local matched = enumItemFromNumber(Enum.FontStyle:GetEnumItems(), source.style)
|
|
535
|
-
if matched then style = matched end
|
|
536
|
-
elseif type(source.style) == "string" then
|
|
537
|
-
local matched = enumItemFromName(Enum.FontStyle:GetEnumItems(), source.style)
|
|
538
|
-
if matched then style = matched end
|
|
539
|
-
end
|
|
540
|
-
|
|
541
|
-
return Font.new(source.family, weight, style)
|
|
542
|
-
end
|
|
543
|
-
if valueType == "PhysicalProperties" then
|
|
544
|
-
if payload == nil then return nil end
|
|
545
|
-
local source = if type(payload) == "table" then payload else record
|
|
546
|
-
return PhysicalProperties.new(
|
|
547
|
-
source.density,
|
|
548
|
-
source.friction,
|
|
549
|
-
source.elasticity,
|
|
550
|
-
source.frictionWeight,
|
|
551
|
-
source.elasticityWeight
|
|
552
|
-
)
|
|
553
|
-
end
|
|
554
|
-
if valueType == "Axes" then
|
|
555
|
-
local source = if type(payload) == "table" then payload else record
|
|
556
|
-
return Axes.new(source.x, source.y, source.z)
|
|
557
|
-
end
|
|
558
|
-
if valueType == "Faces" then
|
|
559
|
-
local source = if type(payload) == "table" then payload else record
|
|
560
|
-
return Faces.new(source.top, source.bottom, source.left, source.right, source.front, source.back)
|
|
561
|
-
end
|
|
562
|
-
if valueType == "Ray" then
|
|
563
|
-
local source = if type(payload) == "table" then payload else record
|
|
564
|
-
local origin = Serializer.deserializeValue(source.origin, options)
|
|
565
|
-
local direction = Serializer.deserializeValue(source.direction, options)
|
|
566
|
-
if typeof(origin) == "Vector3" and typeof(direction) == "Vector3" then return Ray.new(origin, direction) end
|
|
567
|
-
return nil
|
|
568
|
-
end
|
|
569
|
-
if valueType == "InstanceRef" then
|
|
570
|
-
local source = if type(payload) == "table" then payload else record
|
|
571
|
-
local guid = if type(source.guid) == "string" then source.guid else nil
|
|
572
|
-
local path = if type(source.path) == "table" then source.path else nil
|
|
573
|
-
|
|
574
|
-
if guid and options and options.resolveInstanceByGuid then
|
|
575
|
-
local resolvedByGuid = options.resolveInstanceByGuid(guid)
|
|
576
|
-
if resolvedByGuid then return resolvedByGuid end
|
|
577
|
-
end
|
|
578
|
-
|
|
579
|
-
if path then
|
|
580
|
-
if options and options.resolveInstanceByPath then
|
|
581
|
-
local resolvedByPath = options.resolveInstanceByPath(path)
|
|
582
|
-
if resolvedByPath then return resolvedByPath end
|
|
583
|
-
end
|
|
584
|
-
|
|
585
|
-
local resolvedByDefaultPath = findInstanceByPath(path)
|
|
586
|
-
if resolvedByDefaultPath then return resolvedByDefaultPath end
|
|
587
|
-
end
|
|
588
|
-
|
|
589
|
-
return nil
|
|
590
|
-
end
|
|
591
|
-
if valueType == "Region3" then
|
|
592
|
-
local source = if type(payload) == "table" then payload else record
|
|
593
|
-
if type(source.min) ~= "table" or type(source.max) ~= "table" then return nil end
|
|
594
|
-
return Region3.new(
|
|
595
|
-
Vector3.new(source.min.x, source.min.y, source.min.z),
|
|
596
|
-
Vector3.new(source.max.x, source.max.y, source.max.z)
|
|
597
|
-
)
|
|
598
|
-
end
|
|
599
|
-
if valueType == "Region3int16" then
|
|
600
|
-
local source = if type(payload) == "table" then payload else record
|
|
601
|
-
if type(source.min) ~= "table" or type(source.max) ~= "table" then return nil end
|
|
602
|
-
return Region3int16.new(
|
|
603
|
-
Vector3int16.new(source.min.x, source.min.y, source.min.z),
|
|
604
|
-
Vector3int16.new(source.max.x, source.max.y, source.max.z)
|
|
605
|
-
)
|
|
606
|
-
end
|
|
607
|
-
|
|
608
|
-
return nil
|
|
609
|
-
end
|
|
610
|
-
|
|
611
|
-
local function toSet(items: { string }): { [string]: boolean }
|
|
612
|
-
local set = {}
|
|
613
|
-
for _, item in ipairs(items) do
|
|
614
|
-
set[item] = true
|
|
615
|
-
end
|
|
616
|
-
return set
|
|
617
|
-
end
|
|
618
|
-
|
|
619
|
-
local function buildSerializableProperties(): { string }
|
|
620
|
-
if not ReflectionService then return FALLBACK_SERIALIZABLE_PROPERTIES end
|
|
621
|
-
|
|
622
|
-
local classOk, classes = pcall(function()
|
|
623
|
-
return ReflectionService:GetClasses()
|
|
624
|
-
end)
|
|
625
|
-
|
|
626
|
-
if not classOk or type(classes) ~= "table" then return FALLBACK_SERIALIZABLE_PROPERTIES end
|
|
627
|
-
|
|
628
|
-
local propertySet: { [string]: boolean } = {}
|
|
629
|
-
|
|
630
|
-
for _, classInfo in ipairs(classes) do
|
|
631
|
-
local className = classInfo.Name
|
|
632
|
-
if type(className) ~= "string" then continue end
|
|
633
|
-
|
|
634
|
-
local propsOk, reflectedProperties = pcall(function()
|
|
635
|
-
return ReflectionService:GetPropertiesOfClass(className, {
|
|
636
|
-
ExcludeDisplay = true,
|
|
637
|
-
})
|
|
638
|
-
end)
|
|
639
|
-
|
|
640
|
-
if not propsOk or type(reflectedProperties) ~= "table" then continue end
|
|
641
|
-
|
|
642
|
-
for _, reflected in ipairs(reflectedProperties) do
|
|
643
|
-
local reflectedName = reflected.Name
|
|
644
|
-
if type(reflectedName) ~= "string" then continue end
|
|
645
|
-
|
|
646
|
-
if
|
|
647
|
-
reflected.Serialized == true
|
|
648
|
-
and reflectedName ~= "Name"
|
|
649
|
-
and reflectedName ~= "Parent"
|
|
650
|
-
and reflectedName ~= "Source"
|
|
651
|
-
then
|
|
652
|
-
propertySet[reflectedName] = true
|
|
653
|
-
end
|
|
654
|
-
end
|
|
655
|
-
end
|
|
656
|
-
|
|
657
|
-
for _, fallbackProperty in ipairs(FALLBACK_SERIALIZABLE_PROPERTIES) do
|
|
658
|
-
propertySet[fallbackProperty] = true
|
|
659
|
-
end
|
|
660
|
-
|
|
661
|
-
local properties = {}
|
|
662
|
-
for propertyName in pairs(propertySet) do
|
|
663
|
-
table.insert(properties, propertyName)
|
|
664
|
-
end
|
|
665
|
-
|
|
666
|
-
table.sort(properties)
|
|
667
|
-
|
|
668
|
-
if #properties == 0 then return FALLBACK_SERIALIZABLE_PROPERTIES end
|
|
669
|
-
|
|
670
|
-
return properties
|
|
671
|
-
end
|
|
672
|
-
|
|
673
|
-
function Serializer.getSerializableProperties(): { string }
|
|
674
|
-
local cached = SERIALIZABLE_PROPERTIES_CACHE
|
|
675
|
-
if cached then return cached end
|
|
676
|
-
|
|
677
|
-
local built = buildSerializableProperties()
|
|
678
|
-
SERIALIZABLE_PROPERTIES_CACHE = built
|
|
679
|
-
return built
|
|
680
|
-
end
|
|
681
|
-
|
|
682
|
-
local function getSerializablePropertySet(): { [string]: boolean }
|
|
683
|
-
local cached = SERIALIZABLE_PROPERTY_SET_CACHE
|
|
684
|
-
if cached then return cached end
|
|
685
|
-
|
|
686
|
-
local built = toSet(Serializer.getSerializableProperties())
|
|
687
|
-
SERIALIZABLE_PROPERTY_SET_CACHE = built
|
|
688
|
-
return built
|
|
689
|
-
end
|
|
690
|
-
|
|
691
|
-
function Serializer.getWritableSerializablePropertySetForClass(className: string): { [string]: boolean }
|
|
692
|
-
local cached = writablePropertiesByClass[className]
|
|
693
|
-
if cached then return cached end
|
|
694
|
-
|
|
695
|
-
local serializablePropertySet = getSerializablePropertySet()
|
|
696
|
-
local fallback = {}
|
|
697
|
-
for propertyName in pairs(serializablePropertySet) do
|
|
698
|
-
fallback[propertyName] = true
|
|
699
|
-
end
|
|
700
|
-
|
|
701
|
-
if not ReflectionService then
|
|
702
|
-
writablePropertiesByClass[className] = fallback
|
|
703
|
-
return fallback
|
|
704
|
-
end
|
|
705
|
-
|
|
706
|
-
local ok, reflectedProperties = pcall(function()
|
|
707
|
-
return ReflectionService:GetPropertiesOfClass(className)
|
|
708
|
-
end)
|
|
709
|
-
|
|
710
|
-
if not ok or type(reflectedProperties) ~= "table" then
|
|
711
|
-
writablePropertiesByClass[className] = fallback
|
|
712
|
-
return fallback
|
|
713
|
-
end
|
|
714
|
-
|
|
715
|
-
local filtered = {}
|
|
716
|
-
for _, reflected in ipairs(reflectedProperties) do
|
|
717
|
-
local reflectedName = reflected.Name
|
|
718
|
-
if type(reflectedName) ~= "string" then continue end
|
|
719
|
-
|
|
720
|
-
if not serializablePropertySet[reflectedName] then continue end
|
|
721
|
-
|
|
722
|
-
local includedByFallback = FALLBACK_SERIALIZABLE_PROPERTY_SET[reflectedName] == true
|
|
723
|
-
if reflected.Serialized ~= true and not includedByFallback then continue end
|
|
724
|
-
|
|
725
|
-
filtered[reflectedName] = true
|
|
726
|
-
end
|
|
727
|
-
|
|
728
|
-
if next(filtered) == nil then filtered = fallback end
|
|
729
|
-
|
|
730
|
-
writablePropertiesByClass[className] = filtered
|
|
731
|
-
return filtered
|
|
732
|
-
end
|
|
733
|
-
|
|
734
|
-
local function getCanonicalPropertyByLowerForClass(className: string): { [string]: string }
|
|
735
|
-
local cached = canonicalPropertyByLowerByClass[className]
|
|
736
|
-
if cached then return cached end
|
|
737
|
-
|
|
738
|
-
local allowed = Serializer.getWritableSerializablePropertySetForClass(className)
|
|
739
|
-
local byLower = {}
|
|
740
|
-
for propertyName in pairs(allowed) do
|
|
741
|
-
byLower[string.lower(propertyName)] = propertyName
|
|
742
|
-
end
|
|
743
|
-
|
|
744
|
-
canonicalPropertyByLowerByClass[className] = byLower
|
|
745
|
-
return byLower
|
|
746
|
-
end
|
|
747
|
-
|
|
748
|
-
local function resolveWritablePropertyName(instance: Instance, propertyName: string): string?
|
|
749
|
-
local allowed = Serializer.getWritableSerializablePropertySetForClass(instance.ClassName)
|
|
750
|
-
if allowed[propertyName] then return propertyName end
|
|
751
|
-
|
|
752
|
-
local canonicalByLower = getCanonicalPropertyByLowerForClass(instance.ClassName)
|
|
753
|
-
return canonicalByLower[string.lower(propertyName)]
|
|
754
|
-
end
|
|
755
|
-
|
|
756
|
-
local function serializedValuesEqual(left, right): boolean
|
|
757
|
-
if type(left) ~= type(right) then return false end
|
|
758
|
-
|
|
759
|
-
if type(left) ~= "table" then return left == right end
|
|
760
|
-
|
|
761
|
-
for key, value in pairs(left) do
|
|
762
|
-
if not serializedValuesEqual(value, right[key]) then return false end
|
|
763
|
-
end
|
|
764
|
-
|
|
765
|
-
for key in pairs(right) do
|
|
766
|
-
if left[key] == nil then return false end
|
|
767
|
-
end
|
|
768
|
-
|
|
769
|
-
return true
|
|
770
|
-
end
|
|
771
|
-
|
|
772
|
-
local function readAndSerializeProperty(
|
|
773
|
-
instance: Instance,
|
|
774
|
-
propertyName: string,
|
|
775
|
-
options: {
|
|
776
|
-
getInstanceRefData: ((Instance) -> { guid: string?, path: { string }? }?)?,
|
|
777
|
-
}?
|
|
778
|
-
)
|
|
779
|
-
local ok, value = pcall(function()
|
|
780
|
-
return (instance :: any)[propertyName]
|
|
781
|
-
end)
|
|
782
|
-
if not ok then return nil end
|
|
783
|
-
|
|
784
|
-
return Serializer.serializeValue(value, options)
|
|
785
|
-
end
|
|
786
|
-
|
|
787
|
-
local function getDefaultSerializedPropertiesForClass(className: string): { [string]: any }
|
|
788
|
-
local cached = defaultSerializedPropertiesByClass[className]
|
|
789
|
-
if cached then return cached end
|
|
790
|
-
|
|
791
|
-
local defaults = {}
|
|
792
|
-
local ok, tempInstance = pcall(function()
|
|
793
|
-
return Instance.new(className)
|
|
794
|
-
end)
|
|
795
|
-
|
|
796
|
-
if not ok or not tempInstance then
|
|
797
|
-
defaultSerializedPropertiesByClass[className] = defaults
|
|
798
|
-
return defaults
|
|
799
|
-
end
|
|
800
|
-
|
|
801
|
-
local allowedProperties = Serializer.getWritableSerializablePropertySetForClass(className)
|
|
802
|
-
for propertyName in pairs(allowedProperties) do
|
|
803
|
-
local serialized = readAndSerializeProperty(tempInstance, propertyName)
|
|
804
|
-
if serialized ~= nil then defaults[propertyName] = serialized end
|
|
805
|
-
end
|
|
806
|
-
|
|
807
|
-
tempInstance:Destroy()
|
|
808
|
-
defaultSerializedPropertiesByClass[className] = defaults
|
|
809
|
-
return defaults
|
|
810
|
-
end
|
|
811
|
-
|
|
812
|
-
function Serializer.collectSerializedProperties(
|
|
813
|
-
instance: Instance,
|
|
814
|
-
options: {
|
|
815
|
-
getInstanceRefData: ((Instance) -> { guid: string?, path: { string }? }?)?,
|
|
816
|
-
}?
|
|
817
|
-
): { [string]: any }
|
|
818
|
-
local properties = {}
|
|
819
|
-
local className = instance.ClassName
|
|
820
|
-
local allowedProperties = Serializer.getWritableSerializablePropertySetForClass(className)
|
|
821
|
-
local defaultProperties = getDefaultSerializedPropertiesForClass(className)
|
|
822
|
-
local serializableProperties = Serializer.getSerializableProperties()
|
|
823
|
-
|
|
824
|
-
for _, propertyName in ipairs(serializableProperties) do
|
|
825
|
-
if not allowedProperties[propertyName] then continue end
|
|
826
|
-
|
|
827
|
-
if propertyName == "Name" or propertyName == "Parent" then continue end
|
|
828
|
-
if propertyName == "Transparency" and instance:IsA("GuiObject") then continue end
|
|
829
|
-
|
|
830
|
-
local serialized = readAndSerializeProperty(instance, propertyName, options)
|
|
831
|
-
if serialized ~= nil then
|
|
832
|
-
local defaultSerialized = defaultProperties[propertyName]
|
|
833
|
-
if defaultSerialized == nil or not serializedValuesEqual(serialized, defaultSerialized) then
|
|
834
|
-
properties[propertyName] = serialized
|
|
835
|
-
end
|
|
836
|
-
end
|
|
837
|
-
end
|
|
838
|
-
|
|
839
|
-
return properties
|
|
840
|
-
end
|
|
841
|
-
|
|
842
|
-
function Serializer.applySerializedProperties(
|
|
843
|
-
instance: Instance,
|
|
844
|
-
properties: { [string]: any }?,
|
|
845
|
-
options: {
|
|
846
|
-
resolveInstanceByGuid: ((string) -> Instance?)?,
|
|
847
|
-
resolveInstanceByPath: (({ string }) -> Instance?)?,
|
|
848
|
-
}?
|
|
849
|
-
)
|
|
850
|
-
if type(properties) ~= "table" then return end
|
|
851
|
-
|
|
852
|
-
for propertyName, serialized in pairs(properties) do
|
|
853
|
-
if propertyName ~= "Name" and propertyName ~= "Parent" and propertyName ~= "Source" then
|
|
854
|
-
local targetPropertyName = propertyName
|
|
855
|
-
if propertyName == "Transparency" and instance:IsA("GuiObject") then
|
|
856
|
-
targetPropertyName = "BackgroundTransparency"
|
|
857
|
-
end
|
|
858
|
-
|
|
859
|
-
local resolvedProperty = resolveWritablePropertyName(instance, targetPropertyName)
|
|
860
|
-
if not resolvedProperty then continue end
|
|
861
|
-
|
|
862
|
-
local deserialized = Serializer.deserializeValue(serialized, options)
|
|
863
|
-
if deserialized ~= nil then
|
|
864
|
-
pcall(function()
|
|
865
|
-
(instance :: any)[resolvedProperty] = deserialized
|
|
866
|
-
end)
|
|
867
|
-
end
|
|
868
|
-
end
|
|
869
|
-
end
|
|
870
|
-
end
|
|
871
|
-
|
|
872
|
-
function Serializer.applySerializedAttributes(instance: Instance, attributes: { [string]: any }?)
|
|
873
|
-
if type(attributes) ~= "table" then return end
|
|
874
|
-
|
|
875
|
-
for key, value in pairs(attributes) do
|
|
876
|
-
pcall(function()
|
|
877
|
-
instance:SetAttribute(key, value)
|
|
878
|
-
end)
|
|
879
|
-
end
|
|
880
|
-
end
|
|
881
|
-
|
|
882
|
-
function Serializer.collectSerializedTags(instance: Instance): { string }
|
|
883
|
-
if not CollectionService then return {} end
|
|
884
|
-
|
|
885
|
-
local ok, tags = pcall(function()
|
|
886
|
-
return CollectionService:GetTags(instance)
|
|
887
|
-
end)
|
|
888
|
-
|
|
889
|
-
if not ok or type(tags) ~= "table" then return {} end
|
|
890
|
-
|
|
891
|
-
if #tags > 1 then table.sort(tags) end
|
|
892
|
-
return tags
|
|
893
|
-
end
|
|
894
|
-
|
|
895
|
-
function Serializer.applySerializedTags(instance: Instance, tags: { string }?)
|
|
896
|
-
if type(tags) ~= "table" or not CollectionService then return end
|
|
897
|
-
|
|
898
|
-
local existingTagSet: { [string]: boolean } = {}
|
|
899
|
-
local okExisting, existing = pcall(function()
|
|
900
|
-
return CollectionService:GetTags(instance)
|
|
901
|
-
end)
|
|
902
|
-
if okExisting and type(existing) == "table" then
|
|
903
|
-
for _, tagName in ipairs(existing) do
|
|
904
|
-
existingTagSet[tagName] = true
|
|
905
|
-
end
|
|
906
|
-
end
|
|
907
|
-
|
|
908
|
-
local incomingTagSet: { [string]: boolean } = {}
|
|
909
|
-
for _, tagName in ipairs(tags) do
|
|
910
|
-
if type(tagName) == "string" and tagName ~= "" then
|
|
911
|
-
incomingTagSet[tagName] = true
|
|
912
|
-
if not existingTagSet[tagName] then
|
|
913
|
-
pcall(function()
|
|
914
|
-
CollectionService:AddTag(instance, tagName)
|
|
915
|
-
end)
|
|
916
|
-
end
|
|
917
|
-
end
|
|
918
|
-
end
|
|
919
|
-
|
|
920
|
-
for existingTagName in pairs(existingTagSet) do
|
|
921
|
-
if not incomingTagSet[existingTagName] then
|
|
922
|
-
pcall(function()
|
|
923
|
-
CollectionService:RemoveTag(instance, existingTagName)
|
|
924
|
-
end)
|
|
925
|
-
end
|
|
926
|
-
end
|
|
927
|
-
end
|
|
928
|
-
|
|
929
|
-
return Serializer
|