-- This module displays a radial (circular) progress indicator based on images and config information
-- that is generated at https://eryn.io/RadialSpriteSheetGenerator/
-- @readme https://github.com/evaera/RadialSpriteSheetGenerator/blob/master/README.md
-- @author evaera
local HttpService = game:GetService("HttpService")
local ContentProvider = game:GetService("ContentProvider")
local RadialImage = { _version = 1 }
RadialImage.__index = RadialImage
local ConfigurationProperties = {
version = "number";
size = "number";
count = "number";
columns = "number";
rows = "number";
images = "table";
}
function RadialImage.new(config, label)
if type(config) == "string" then
config = HttpService:JSONDecode(config)
elseif type(config) ~= "table" then
error("Argument #1 (configuration) must be a JSON string or table.", 2)
end
for k, v in pairs(config) do
if ConfigurationProperties[k] == nil then
error(("Invalid property name in Radial Image configuration: %s"):format(k), 2)
end
if type(v) ~= ConfigurationProperties[k] then
error(("Invalid property type %q in Radial Image configuration: must be a %s."):format(k, ConfigurationProperties[k]), 2)
end
end
if config.version ~= RadialImage._version then
error(("Passed configuration version does not match this module's version (which is %d)"):format(RadialImage._version), 2)
end
local self = { config = config; label = label }
setmetatable(self, RadialImage)
return self
end
function RadialImage:Preload()
if self.label == nil then
error("You must provide a label to RadialImage.new to use Preload", 2)
end
self.labels = {}
for _, image in ipairs(self.config.images) do
local label = self.label:Clone()
label.Image = image
label.Visible = true
label.Size = UDim2.new(0, 0, 0, 0)
label.Parent = self.label.Parent
table.insert(self.labels, label)
end
ContentProvider:PreloadAsync(self.labels)
for _, label in ipairs(self.labels) do
label.Visible = false
end
end
function RadialImage:Destroy()
for _, label in ipairs(self.labels) do
label:Destroy()
end
self.labels = nil
end
function RadialImage:GetFromAlpha(alpha)
if type(alpha) ~= "number" then
error("Argument #1 (alpha) to GetFromAlpha must be a number.", 2)
end
local count, size, columns, rows = self.config.count, self.config.size, self.config.columns, self.config.rows
local index = alpha >= 1 and count - 1 or math.floor(alpha * count)
local page = math.floor(index / (columns * rows)) + 1
local pageIndex = index - (columns * rows * (page - 1))
local x = (pageIndex % columns) * size
local y = math.floor(pageIndex / columns) * size
return x, y, page
end
function RadialImage:UpdateLabel(alpha, label)
label = label or self.label
if type(alpha) ~= "number" then
error("Argument #1 (alpha) to UpdateLabel must be a number.", 2)
end
if typeof(label) ~= "Instance" or not (label:IsA("ImageLabel") or label:IsA("ImageButton")) then
error("Attempt to update label but no label has been given. Either pass the label as argument #2 to \"new\", or as argument #2 to \"UpdateLabel\".", 2)
end
local x, y, page = self:GetFromAlpha(alpha)
label.ImageRectSize = Vector2.new(self.config.size, self.config.size)
label.ImageRectOffset = Vector2.new(x, y)
label.Image = alpha <= 0 and "" or self.config.images[page]
end
return RadialImage
local module = {}
local Players = game:GetService("Players")
local h = {...}
local PhysicsService = game:GetService("PhysicsService")
local ragParts = h[1]
function module:Setup(char: Model)
assert(
Players:GetPlayerFromCharacter(char) == nil,
"Setting up ragdoll on player characters is already done automatically"
)
local humanoid = char:FindFirstChild("Humanoid")
assert(humanoid, "Can only set-up ragdoll on R6 humanoid rigs")
assert(humanoid.RigType == Enum.HumanoidRigType.R6, "Can only set-up ragdoll on R6 humanoid rigs")
assert(humanoid.RootPart ~= nil, "No RootPart was found in the provided rig")
assert(char:FindFirstChild("HumanoidRootPart"), "No HumanoidRootPart was found in the provided rig")
for _, v: BasePart in ipairs(char:GetDescendants()) do
if v:IsA("BasePart") then
v:SetNetworkOwner(nil)
end
end
-- Setup ragdoll
char.Head.Size = Vector3.new(1, 1, 1)
humanoid.BreakJointsOnDeath = false
humanoid.RequiresNeck = false
local clones = {}
for _, v in ipairs(ragParts) do
clones[v.Name] = v:Clone()
end
local folder1 = Instance.new("Folder")
folder1.Name = "RagdollConstraints"
for _, v in pairs(clones) do
if v:IsA("Attachment") then
v.Parent = char[v:GetAttribute("Parent")]
elseif v:IsA("BallSocketConstraint") then
v.Attachment0 = clones[v:GetAttribute("0")]
v.Attachment1 = clones[v:GetAttribute("1")]
v.Parent = folder1
end
end
folder1.Parent = char
local folder2 = Instance.new("Folder")
folder2.Name = "Motors"
local value
for _, v in ipairs(char.Torso:GetChildren()) do
if v:IsA("Motor6D") then
value = Instance.new("ObjectValue")
value.Value = v
value.Parent = folder2
end
end
folder2.Parent = folder1
-- Ragdoll trigger
local trigger = Instance.new("BoolValue")
trigger.Name = "RagdollTrigger"
trigger.Parent = char
trigger.Changed:Connect(function(bool)
if bool then
module:Ragdoll(char)
else
module:Unragdoll(char)
end
end)
end
function module:Ragdoll(char: Model)
char.Humanoid:ChangeState(Enum.HumanoidStateType.Physics)
char.Humanoid.AutoRotate = false
for _, v in ipairs(char.RagdollConstraints.Motors:GetChildren()) do
v.Value.Enabled = false
end
for _, v in ipairs(char:GetChildren()) do
if v:IsA("BasePart") then
PhysicsService:SetPartCollisionGroup(v, "Ragdoll")
end
end
char.HumanoidRootPart:ApplyAngularImpulse(Vector3.new(-90, 0, 0))
end
function module:Unragdoll(char: Model)
char.Humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
for _, v in ipairs(char.RagdollConstraints.Motors:GetChildren()) do
v.Value.Enabled = true
end
for _, v in ipairs(char:GetChildren()) do
if v:IsA("BasePart") then
PhysicsService:SetPartCollisionGroup(v, "Default")
end
end
char.Humanoid.AutoRotate = true
end
return module
-- Converted using Mokiros's Model to Script Version 3
-- Converted string size: 1392 characters
local function Decode(str)
local StringLength = #str
-- Base64 decoding
do
local decoder = {}
for b64code, char in pairs(('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='):split('')) do
decoder[char:byte()] = b64code-1
end
local n = StringLength
local t,k = table.create(math.floor(n/4)+1),1
local padding = str:sub(-2) == '==' and 2 or str:sub(-1) == '=' and 1 or 0
for i = 1, padding > 0 and n-4 or n, 4 do
local a, b, c, d = str:byte(i,i+3)
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
t[k] = string.char(bit32.extract(v,16,8),bit32.extract(v,8,8),bit32.extract(v,0,8))
k = k + 1
end
if padding == 1 then
local a, b, c = str:byte(n-3,n-1)
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40
t[k] = string.char(bit32.extract(v,16,8),bit32.extract(v,8,8))
elseif padding == 2 then
local a, b = str:byte(n-3,n-2)
local v = decoder[a]*0x40000 + decoder[b]*0x1000
t[k] = string.char(bit32.extract(v,16,8))
end
str = table.concat(t)
end
local Position = 1
local function Parse(fmt)
local Values = {string.unpack(fmt,str,Position)}
Position = table.remove(Values)
return table.unpack(Values)
end
local Settings = Parse('B')
local Flags = Parse('B')
Flags = {
--[[ValueIndexByteLength]] bit32.extract(Flags,6,2)+1,
--[[InstanceIndexByteLength]] bit32.extract(Flags,4,2)+1,
--[[ConnectionsIndexByteLength]] bit32.extract(Flags,2,2)+1,
--[[MaxPropertiesLengthByteLength]] bit32.extract(Flags,0,2)+1,
--[[Use Double instead of Float]] bit32.band(Settings,0b1) > 0
}
local ValueFMT = ('I'..Flags[1])
local InstanceFMT = ('I'..Flags[2])
local ConnectionFMT = ('I'..Flags[3])
local PropertyLengthFMT = ('I'..Flags[4])
local ValuesLength = Parse(ValueFMT)
local Values = table.create(ValuesLength)
local CFrameIndexes = {}
local ValueDecoders = {
--!!Start
[1] = function(Modifier)
return Parse('s'..Modifier)
end,
--!!Split
[2] = function(Modifier)
return Modifier ~= 0
end,
--!!Split
[3] = function()
return Parse('d')
end,
--!!Split
[4] = function(_,Index)
table.insert(CFrameIndexes,{Index,Parse(('I'..Flags[1]):rep(3))})
end,
--!!Split
[5] = {CFrame.new,Flags[5] and 'dddddddddddd' or 'ffffffffffff'},
--!!Split
[6] = {Color3.fromRGB,'BBB'},
--!!Split
[7] = {BrickColor.new,'I2'},
--!!Split
[8] = function(Modifier)
local len = Parse('I'..Modifier)
local kpts = table.create(len)
for i = 1,len do
kpts[i] = ColorSequenceKeypoint.new(Parse('f'),Color3.fromRGB(Parse('BBB')))
end
return ColorSequence.new(kpts)
end,
--!!Split
[9] = function(Modifier)
local len = Parse('I'..Modifier)
local kpts = table.create(len)
for i = 1,len do
kpts[i] = NumberSequenceKeypoint.new(Parse(Flags[5] and 'ddd' or 'fff'))
end
return NumberSequence.new(kpts)
end,
--!!Split
[10] = {Vector3.new,Flags[5] and 'ddd' or 'fff'},
--!!Split
[11] = {Vector2.new,Flags[5] and 'dd' or 'ff'},
--!!Split
[12] = {UDim2.new,Flags[5] and 'di2di2' or 'fi2fi2'},
--!!Split
[13] = {Rect.new,Flags[5] and 'dddd' or 'ffff'},
--!!Split
[14] = function()
local flags = Parse('B')
local ids = {"Top","Bottom","Left","Right","Front","Back"}
local t = {}
for i = 0,5 do
if bit32.extract(flags,i,1)==1 then
table.insert(t,Enum.NormalId[ids[i+1]])
end
end
return Axes.new(unpack(t))
end,
--!!Split
[15] = function()
local flags = Parse('B')
local ids = {"Top","Bottom","Left","Right","Front","Back"}
local t = {}
for i = 0,5 do
if bit32.extract(flags,i,1)==1 then
table.insert(t,Enum.NormalId[ids[i+1]])
end
end
return Faces.new(unpack(t))
end,
--!!Split
[16] = {PhysicalProperties.new,Flags[5] and 'ddddd' or 'fffff'},
--!!Split
[17] = {NumberRange.new,Flags[5] and 'dd' or 'ff'},
--!!Split
[18] = {UDim.new,Flags[5] and 'di2' or 'fi2'},
--!!Split
[19] = function()
return Ray.new(Vector3.new(Parse(Flags[5] and 'ddd' or 'fff')),Vector3.new(Parse(Flags[5] and 'ddd' or 'fff')))
end
--!!End
}
for i = 1,ValuesLength do
local TypeAndModifier = Parse('B')
local Type = bit32.band(TypeAndModifier,0b11111)
local Modifier = (TypeAndModifier - Type) / 0b100000
local Decoder = ValueDecoders[Type]
if type(Decoder)=='function' then
Values[i] = Decoder(Modifier,i)
else
Values[i] = Decoder[1](Parse(Decoder[2]))
end
end
for i,t in pairs(CFrameIndexes) do
Values[t[1]] = CFrame.fromMatrix(Values[t[2]],Values[t[3]],Values[t[4]])
end
local InstancesLength = Parse(InstanceFMT)
local Instances = {}
local NoParent = {}
for i = 1,InstancesLength do
local ClassName = Values[Parse(ValueFMT)]
local obj
local MeshPartMesh,MeshPartScale
if ClassName == "UnionOperation" then
obj = DecodeUnion(Values,Flags,Parse)
obj.UsePartColor = true
elseif ClassName:find("Script") then
obj = Instance.new("Folder")
Script(obj,ClassName=='ModuleScript')
elseif ClassName == "MeshPart" then
obj = Instance.new("Part")
MeshPartMesh = Instance.new("SpecialMesh")
MeshPartMesh.MeshType = Enum.MeshType.FileMesh
MeshPartMesh.Parent = obj
else
obj = Instance.new(ClassName)
end
local Parent = Instances[Parse(InstanceFMT)]
local PropertiesLength = Parse(PropertyLengthFMT)
local AttributesLength = Parse(PropertyLengthFMT)
Instances[i] = obj
for i = 1,PropertiesLength do
local Prop,Value = Values[Parse(ValueFMT)],Values[Parse(ValueFMT)]
-- ok this looks awful
if MeshPartMesh then
if Prop == "MeshId" then
MeshPartMesh.MeshId = Value
continue
elseif Prop == "TextureID" then
MeshPartMesh.TextureId = Value
continue
elseif Prop == "Size" then
if not MeshPartScale then
MeshPartScale = Value
else
MeshPartMesh.Scale = Value / MeshPartScale
end
elseif Prop == "MeshSize" then
if not MeshPartScale then
MeshPartScale = Value
MeshPartMesh.Scale = obj.Size / Value
else
MeshPartMesh.Scale = MeshPartScale / Value
end
continue
end
end
print(Prop, Value)
obj[Prop] = Value
end
if MeshPartMesh then
if MeshPartMesh.MeshId=='' then
if MeshPartMesh.TextureId=='' then
MeshPartMesh.TextureId = 'rbxasset://textures/meshPartFallback.png'
end
MeshPartMesh.Scale = obj.Size
end
end
for i = 1,AttributesLength do
obj:SetAttribute(Values[Parse(ValueFMT)],Values[Parse(ValueFMT)])
end
if not Parent then
table.insert(NoParent,obj)
else
obj.Parent = Parent
end
end
local ConnectionsLength = Parse(ConnectionFMT)
for i = 1,ConnectionsLength do
local a,b,c = Parse(InstanceFMT),Parse(ValueFMT),Parse(InstanceFMT)
Instances[a][Values[b]] = Instances[c]
end
return NoParent
end
local Objects = Decode('AABUIQRQYXJ0IQROYW1lIQxSYWdkb2xsUGFydHMhCEFuY2hvcmVkIiENQm90dG9tU3VyZmFjZQMAAAAAAAAAACEGQ0ZyYW1lBBBPMyEKQ2FuQ29sbGlkZQIhCENhblRvdWNoIQpDYXN0U2hhZG93IQZMb2NrZWQhCFBvc2l0aW9uCh0pCkMAAAAAjHEEwSEEU2l6ZQoA'
..'AIBAAACAPwAAAEAhClRvcFN1cmZhY2UhCkF0dGFjaG1lbnQhA0xBMAQZUDMhC09yaWVudGF0aW9uCgAAAIAAADRDAAAAAAoAAIC/AACAPwAAAAAhBlBhcmVudCEFVG9yc28hFEJhbGxTb2NrZXRDb25zdHJhaW50IQNMQVMhDUxpbWl0c0VuYWJsZWQhEU1heEZyaWN0'
..'aW9uVG9ycXVlAwAAAAAAAElAIRJUd2lzdExpbWl0c0VuYWJsZWQhD1R3aXN0TG93ZXJBbmdsZQMAAAAAAIBWwCEPVHdpc3RVcHBlckFuZ2xlAwAAAAAAgFZAIQpVcHBlckFuZ2xlIQExIQNMQTEhATAhA0xMMAQtUVIKAAAAgAAAtEIAALTCCgAAAL8AAIC/AAAAACED'
..'TExTIQNMTDEhAk4wBDMzUgoAAACAAAC0wgAAtEIKAAAAAAAAgD8AAAAAIQJOUwMAAAAAAABOQCECTjEhA1JBMAQ5TzMKAACAPwAAgD8AAAAAIQNSQVMhA1JBMSEDUkwwBD5RUgoAAAA/AACAvwAAAAAhA1JMUyEDUkwxBDNTVCEJUmlnaHQgTGVnBEVRTwoAAACAAAAA'
..'gAAAtMIKAAAAvwAAgD8AAAAAIQlSaWdodCBBcm0hCExlZnQgTGVnBEpRUAoAAAAAAAA0wwAAtMIKAAAAPwAAgD8AAAAAIQhMZWZ0IEFybQRNM1IKAAAAAAAAAL8AAAAAIQRIZWFkCgAAgD8AAAAAAAAAAAoAAIC/AAAAAAAAAAAKAAAAAAAAgL8AAAAACgAAAAAAAAAA'
..'AACAvwrtrQknAACAvy69OzMKLr07sy69O7MAAIC/EAEACwACAwQFBgcICQoLDAsNCw4FDxAREhMHFAEEAQIVCBYXGA8ZGhscAQcCAh0eBR8gIQUiIyQlJiUnKCkVFAEEAQIqCCsXLA8tGhscAQUCAi4eBR8gIQUmJScvKSoUAQQBAjAIMRcyDzMaGxwBBQICNB4FHyAh'
..'BSY1JzYpMBQBAwECNwg4DzkaGxwBBwICOh4FHyAhBSIjJCUmJSc7KTcUAQQBAjwIPRcsDz4aGxwBBQICPx4FHyAhBSYlJ0ApPBQBBAECQAhBFywPMxpCFAEEAQI7CEMXRA9FGkYUAQQBAi8IQRcsDzMaRxQBBAECKAhIF0kPShpLFAEEAQI2CEwXMg9NGk4A')
return Objects[1]