add gs
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
JSONSettings = ...
|
||||
local placeId = JSONSettings["PlaceId"]
|
||||
local universeId = JSONSettings["UniverseId"]
|
||||
local matchmakingContextId = JSONSettings["MatchmakingContextId"]
|
||||
local gameCode = JSONSettings["GameCode"]
|
||||
local baseUrl = JSONSettings["BaseUrl"]
|
||||
local gameId = JSONSettings["GameId"]
|
||||
local machineAddress = JSONSettings["MachineAddress"]
|
||||
local gsmInterval = JSONSettings["GsmInterval"]
|
||||
local maxPlayers = JSONSettings["MaxPlayers"]
|
||||
local maxGameInstances = JSONSettings["MaxGameInstances"]
|
||||
local apiKey = JSONSettings["ApiKey"]
|
||||
local preferredPlayerCapacity = JSONSettings["PreferredPlayerCapacity"]
|
||||
local placeVisitAccessKey = JSONSettings["PlaceVisitAccessKey"]
|
||||
|
||||
local access = true
|
||||
local isCloudEdit = matchmakingContextId == 3
|
||||
local assetGameUrl = "https://assetgame." .. baseUrl
|
||||
|
||||
-- Monitor Game Status LUA Script
|
||||
-- Reports game stats to a web handler on regular intervals
|
||||
|
||||
-- Assumes the following local variables have been defined:
|
||||
-- url - root url for the environment (e.g. www.roblox.com)
|
||||
-- placeId - place being run
|
||||
-- maxPlayers - maximum number of players allowed in this game
|
||||
-- preferredPlayerCapacity - number of player slots reserved for social matchmaking
|
||||
-- machineAddress - IP address of the server
|
||||
-- gsmInterval - send the message no matter what on this interval
|
||||
-- baseUrl - base url to build other urls
|
||||
-- isCloudEdit - if the current server context is cloudEdit
|
||||
-- -----------------------------------------------------------
|
||||
|
||||
startTime = tick()
|
||||
print("yo yo does this work??")
|
||||
networkServer = game:GetService("NetworkServer")
|
||||
playersService = game:GetService("Players")
|
||||
httpService = game:GetService("HttpService")
|
||||
pcall(function() playersService.MaxPlayers = maxPlayers end)
|
||||
pcall(function() playersService.MaxPlayersInternal = maxPlayers end)
|
||||
pcall(function() playersService.PreferredPlayersInternal = preferredPlayerCapacity end)
|
||||
gsmUrl = "https://games.api." .. baseUrl
|
||||
gameInstancesApiUrl = "https://gameinstances.api." .. baseUrl
|
||||
apiProxyUrl = "https://api." .. baseUrl
|
||||
playerJoinTimes = {}
|
||||
|
||||
local RCCKickDupeExists, RCCKickDupeEnabled = pcall(function()
|
||||
return settings():GetFFlag("RCCKickDuplicatePlayersOnJoin")
|
||||
end)
|
||||
|
||||
local kickDuplicatePlayersRCC = RCCKickDupeExists and RCCKickDupeEnabled and not isCloudEdit;
|
||||
|
||||
function UrlEncode(s)
|
||||
s = string.gsub(s, "([&=+%c])", function (c)
|
||||
return string.format("%%%02X", string.byte(c))
|
||||
end)
|
||||
s = string.gsub(s, " ", "+")
|
||||
return s
|
||||
end
|
||||
|
||||
|
||||
function Split(str, pat)
|
||||
local t = {} -- NOTE: use {n = 0} in Lua-5.0
|
||||
local fpat = "(.-)" .. pat
|
||||
local last_end = 1
|
||||
local s, e, cap = str:find(fpat, 1)
|
||||
while s do
|
||||
if s ~= 1 or cap ~= "" then
|
||||
table.insert(t,cap)
|
||||
end
|
||||
last_end = e + 1
|
||||
s, e, cap = str:find(fpat, last_end)
|
||||
end
|
||||
if last_end <= #str then
|
||||
cap = str:sub(last_end)
|
||||
table.insert(t, cap)
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function CalculateAveragePing()
|
||||
local totalPing = 0
|
||||
local replicatorCount = 0
|
||||
local averagePing = 0
|
||||
local status, err = pcall(function()
|
||||
for _, r in ipairs(stats().Network:GetChildren()) do
|
||||
if r.Name ~= "Packets Thread" then
|
||||
r:GetValue() -- hax
|
||||
totalPing = totalPing + r.Ping:GetValue()
|
||||
replicatorCount = replicatorCount + 1
|
||||
end
|
||||
end
|
||||
if replicatorCount > 0 then
|
||||
averagePing = totalPing / replicatorCount
|
||||
end
|
||||
end)
|
||||
if (not status) then
|
||||
PrintDebugMessage("CalculateAveragePing error = " .. err)
|
||||
end
|
||||
return averagePing
|
||||
end
|
||||
|
||||
|
||||
function PrintDebugMessage(message)
|
||||
if message then
|
||||
-- print ("!GameServiceMonitor: " .. message)
|
||||
-- game:HttpPost(gsmUrl .. "/v1.0/LogLuaMessage?&apiKey=" .. apiKey .. "&text=" .. UrlEncode(message), "")
|
||||
end
|
||||
end
|
||||
|
||||
function PrintErrorMessage(message)
|
||||
if message then
|
||||
print ("!GameServiceMonitor: " .. message)
|
||||
-- game:HttpPost(gsmUrl .. "/v1.0/LogLuaMessage?&apiKey=" .. apiKey .. "&text=" .. UrlEncode(message), "")
|
||||
end
|
||||
end
|
||||
|
||||
function UpdatePresence(player, isDisconnect)
|
||||
local fullUrl = apiProxyUrl .. "/presence/"
|
||||
local queryParams = "?visitorId=" .. player.userId
|
||||
|
||||
if isDisconnect then
|
||||
fullUrl = fullUrl .. "register-absence"
|
||||
else
|
||||
fullUrl = fullUrl .. "register-game-presence"
|
||||
queryParams = queryParams .. "&placeId=" .. placeId .. "&gameId=" .. gameId .. "&locationType=" .. ((isCloudEdit or matchmakingContextId == 4) and "CloudEdit" or "Game")
|
||||
end
|
||||
|
||||
fullUrl = fullUrl .. queryParams
|
||||
|
||||
PrintDebugMessage("Calling Api Proxy to update presence. URL: " .. fullUrl)
|
||||
|
||||
game:HttpPost(fullUrl, "")
|
||||
return true
|
||||
end
|
||||
|
||||
function GetPlayerPostData()
|
||||
local sessionArray = {}
|
||||
for _, player in ipairs(playersService:GetPlayers()) do
|
||||
local t =
|
||||
{
|
||||
UserId = player.userId,
|
||||
IsVr = player.VRDevice ~= "",
|
||||
GameTimeWhenJoined = playerJoinTimes[player.userId],
|
||||
GameSessionId = player:GetGameSessionID()
|
||||
}
|
||||
table.insert(sessionArray, t)
|
||||
end
|
||||
return httpService:JSONEncode({GameSessions = sessionArray})
|
||||
end
|
||||
|
||||
function SendMessageToGamesApi(source, player)
|
||||
local postDataJson = ""
|
||||
local playerCSV = ""
|
||||
postDataJson = GetPlayerPostData()
|
||||
|
||||
local w = stats().Workspace
|
||||
local gameTime = tick() - startTime
|
||||
|
||||
local fullUrl = ""
|
||||
fullUrl = gsmUrl .. "/v2.0/Refresh/?apiKey=" .. apiKey
|
||||
fullUrl = fullUrl .. "&gameId=" .. gameId .."&placeId=" .. placeId .."&gameCapacity=" .. maxPlayers
|
||||
fullUrl = fullUrl .. "&maximumGameInstances=" .. maxGameInstances .."&ipAddress=" .. machineAddress
|
||||
fullUrl = fullUrl .. "&port=" .. networkServer.Port .."&clientCount=" .. networkServer:GetClientCount()
|
||||
fullUrl = fullUrl .. "&gameTime=" .. gameTime
|
||||
fullUrl = fullUrl .. "&preferredPlayerCapacity=" .. preferredPlayerCapacity
|
||||
fullUrl = fullUrl .. "&eventSource=" .. source
|
||||
if player ~= nil then
|
||||
fullUrl = fullUrl .. "&originatingPlayerId=" .. player.userId
|
||||
end
|
||||
fullUrl = fullUrl .. "&gameCode="
|
||||
if gameCode ~= nil then
|
||||
fullUrl = fullUrl .. gameCode
|
||||
end
|
||||
fullUrl = fullUrl .. "&matchmakingContextId=" .. matchmakingContextId
|
||||
fullUrl = fullUrl .. "&isCloudEdit=" .. ((isCloudEdit or matchmakingContextId == 4) and "true" or "false")
|
||||
fullUrl = fullUrl .. "&rccVersion=" .. version()
|
||||
|
||||
PrintDebugMessage("Calling Games API. Source: " .. source .. ", URL: " .. fullUrl .. " Post data (JSON}:" .. postDataJson)
|
||||
|
||||
game:HttpPost(fullUrl, postDataJson, false, "application/json")
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function SendMessageToGameInstancesApi(source)
|
||||
local postDataJson = ""
|
||||
postDataJson = GetPlayerPostData()
|
||||
|
||||
local w = stats().Workspace
|
||||
local gameTime = tick() - startTime
|
||||
local averagePing = CalculateAveragePing()
|
||||
|
||||
local fullUrl = ""
|
||||
fullUrl = gameInstancesApiUrl .. "/v2/CreateOrUpdate/?apiKey=" .. apiKey
|
||||
fullUrl = fullUrl .. "&gameId=" .. gameId .."&placeId=" .. placeId .. "&gameCapacity=" .. maxPlayers
|
||||
fullUrl = fullUrl .. "&maximumGameInstances=" .. maxGameInstances .. "&serverIpAddress=" .. machineAddress
|
||||
fullUrl = fullUrl .. "&serverPort=" .. networkServer.Port .. "&fps=" .. w.FPS:GetValue()
|
||||
fullUrl = fullUrl .. "&heartbeatRate=" .. w.Heartbeat:GetValue()
|
||||
fullUrl = fullUrl .. "&ping=" .. averagePing .. "&gameTime=" .. gameTime
|
||||
fullUrl = fullUrl .. "&universeId=" .. universeId
|
||||
fullUrl = fullUrl .. "&gameCode="
|
||||
if gameCode ~= nil then
|
||||
fullUrl = fullUrl .. gameCode
|
||||
end
|
||||
fullUrl = fullUrl .. "&matchmakingContextId=" .. matchmakingContextId
|
||||
|
||||
PrintDebugMessage("Calling Game Instances API. Source: " .. source .. ", URL: " .. fullUrl .. " Post data (json): " .. postDataJson)
|
||||
|
||||
game:HttpPost(fullUrl, postDataJson, false, "application/json")
|
||||
return true
|
||||
end
|
||||
|
||||
-- Send updates to Games API and Game Instances API every gsmInterval seconds
|
||||
delay(0, function()
|
||||
while networkServer.Port == 0 do
|
||||
wait(1)
|
||||
end
|
||||
|
||||
while true do
|
||||
-- always send on gsmInterval, we also send on events when player join and leave.
|
||||
pcall(function() return SendMessageToGamesApi("HeartBeat", nil) end)
|
||||
pcall(function() return SendMessageToGameInstancesApi("HeartBeat") end)
|
||||
wait(gsmInterval)
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
-- Events that make HTTP calls back to endpoints tracking player activity
|
||||
|
||||
local function onPlayerConnectingReportClientPresence(player)
|
||||
if assetGameUrl and access and placeId and player and player.userId then
|
||||
local didTeleportIn = "False"
|
||||
if player.TeleportedIn then didTeleportIn = "True" end
|
||||
|
||||
game:HttpGet(assetGameUrl .. "/Game/ClientPresence.ashx?action=connect&PlaceID=" .. placeId .. "&UserID=" .. player.userId)
|
||||
if not isCloudEdit then
|
||||
game:HttpPost(assetGameUrl .. "/Game/PlaceVisit.ashx?UserID=" .. player.userId .. "&AssociatedPlaceID=" .. placeId .. "&placeVisitAccessKey=" .. placeVisitAccessKey .. "&IsTeleport=" .. didTeleportIn, "")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onPlayerConnectingReportClientPresence2(player)
|
||||
playerJoinTimes[player.userId] = tick() - startTime
|
||||
|
||||
-- Games API
|
||||
local success, err = pcall(function() return SendMessageToGamesApi("PlayerAdded", player) end)
|
||||
if (not success) then
|
||||
PrintErrorMessage("playersService.PlayerAdded error updating games api = " .. err)
|
||||
end
|
||||
|
||||
-- Game Instances API
|
||||
success, err = pcall(function() return SendMessageToGameInstancesApi("PlayerAdded") end )
|
||||
if (not success) then
|
||||
PrintErrorMessage("playersService.PlayerAdded error updating game instances api = " .. err)
|
||||
end
|
||||
|
||||
-- Api Proxy - Presence
|
||||
success, err = pcall(function() return UpdatePresence(player, false) end)
|
||||
if (not success) then
|
||||
PrintErrorMessage("playersService.PlayerAdded error updating presence = " .. err)
|
||||
end
|
||||
end
|
||||
|
||||
local function onPlayerDisconnectingReportClientPresence(player)
|
||||
local isTeleportingOut = "False"
|
||||
if player.Teleported then isTeleportingOut = "True" end
|
||||
|
||||
if assetGameUrl and access and placeId and player and player.userId then
|
||||
game:HttpGet(assetGameUrl .. "/Game/ClientPresence.ashx?action=disconnect&PlaceID=" .. placeId .. "&UserID=" .. player.userId .. "&IsTeleport=" .. isTeleportingOut)
|
||||
end
|
||||
end
|
||||
|
||||
local function onPlayerDisconnectingReportClientPresence2(player)
|
||||
playerJoinTimes[player.userId] = nil
|
||||
|
||||
-- Games API
|
||||
local success, err = pcall(function() return SendMessageToGamesApi("PlayerRemoving", player) end)
|
||||
if (not success) then
|
||||
PrintErrorMessage("playersService.PlayerRemoving error updating games api = " .. err)
|
||||
end
|
||||
|
||||
-- Game Instances API
|
||||
success, err = pcall(function() return SendMessageToGameInstancesApi("PlayerRemoving") end)
|
||||
if (not success) then
|
||||
PrintErrorMessage("playersService.PlayerRemoving error updating game instances api = = " .. err)
|
||||
end
|
||||
|
||||
-- Api Proxy - Presence
|
||||
success, err = pcall(function() return UpdatePresence(player, true) end)
|
||||
if (not success) then
|
||||
PrintErrorMessage("playersService.PlayerRemoving error updating presence = " .. err)
|
||||
end
|
||||
end
|
||||
|
||||
if (kickDuplicatePlayersRCC or isCloudEdit) then
|
||||
playersService.PlayerConnecting:connect(function(player)
|
||||
pcall(function() onPlayerConnectingReportClientPresence2(player) end)
|
||||
pcall(function() onPlayerConnectingReportClientPresence(player) end)
|
||||
end)
|
||||
|
||||
playersService.PlayerDisconnecting:connect(function(player)
|
||||
pcall(function() onPlayerDisconnectingReportClientPresence2(player) end)
|
||||
pcall(function() onPlayerDisconnectingReportClientPresence(player) end)
|
||||
end)
|
||||
else
|
||||
playersService.PlayerAdded:connect(onPlayerConnectingReportClientPresence)
|
||||
playersService.PlayerAdded:connect(onPlayerConnectingReportClientPresence2)
|
||||
|
||||
playersService.PlayerRemoving:connect(onPlayerDisconnectingReportClientPresence)
|
||||
playersService.PlayerRemoving:connect(onPlayerDisconnectingReportClientPresence2)
|
||||
end
|
||||
|
||||
game.Close:connect(function()
|
||||
-- when game closes, notify Game Instances API
|
||||
local fullUrl = gameInstancesApiUrl .. "/v1/Close/?apiKey=" .. apiKey .. "&gameId=" .. gameId .."&placeId=" .. placeId .."&universeId=" .. universeId
|
||||
PrintDebugMessage("Calling Game Instances API. Source: GameClose, URL: " .. fullUrl)
|
||||
game:HttpPost(fullUrl, "", true)
|
||||
end)
|
||||
@@ -0,0 +1,335 @@
|
||||
-- AnimationManifest.txt
|
||||
-- V 1.0.2
|
||||
|
||||
-- Exports multiple frames
|
||||
-- https://spocktopus.roblox.local/Internals_of_3D_Animated_Thumbnails
|
||||
|
||||
baseUrl, characterAppearanceUrl, animationUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
|
||||
local FRAME_RATE = 60
|
||||
local MAX_TIME = 2
|
||||
local originalJointCFramesMap = {}
|
||||
local originalPartsCFramesMap = {}
|
||||
|
||||
local function getOriginalJointCFrames(object)
|
||||
for _, child in pairs(object:GetChildren()) do
|
||||
if child:IsA("Motor6D") then
|
||||
originalJointCFramesMap[child] = child.C1
|
||||
end
|
||||
if child:IsA("BasePart") then
|
||||
originalPartsCFramesMap[child] = child.CFrame
|
||||
end
|
||||
getOriginalJointCFrames(child)
|
||||
end
|
||||
end
|
||||
|
||||
local character = player.Character
|
||||
local humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
getOriginalJointCFrames(character)
|
||||
|
||||
local animator = humanoid:FindFirstChild("Animator")
|
||||
local animationObj = Instance.new("Animation")
|
||||
|
||||
local animationObjects = game:GetObjects(animationUrl)
|
||||
local animations = {}
|
||||
local rotateCharacter = false
|
||||
|
||||
local function getAnimations(model)
|
||||
for _, child in pairs(model:GetChildren()) do
|
||||
if child:IsA("StringValue") and child.Name == "swim" then
|
||||
rotateCharacter = true
|
||||
elseif child:IsA("StringValue") and child.Name == "swimidle" then
|
||||
-- Don't include swimidle if we are thumnailing the swim animation
|
||||
if child.Parent and child.Parent:FindFirstChild("swim") then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if child:IsA("Animation") then
|
||||
table.insert(animations, child)
|
||||
else
|
||||
getAnimations(child)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, animationModel in pairs(animationObjects) do
|
||||
getAnimations(animationModel)
|
||||
end
|
||||
|
||||
if rotateCharacter then
|
||||
local rootPart = character:FindFirstChild("HumanoidRootPart")
|
||||
if rootPart then
|
||||
rootPart.CFrame = rootPart.CFrame * CFrame.Angles(math.rad(-90), 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local animationId = ""
|
||||
local bestWeight = -1
|
||||
for _, anim in pairs(animations) do
|
||||
local weight = anim:FindFirstChild("Weight")
|
||||
if weight then
|
||||
if weight.Value > bestWeight then
|
||||
bestWeight = weight.Value
|
||||
animationId = anim.AnimationId
|
||||
end
|
||||
else
|
||||
animationId = anim.AnimationId
|
||||
end
|
||||
end
|
||||
|
||||
local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider")
|
||||
local keyframeSequence = KeyframeSequenceProvider:GetKeyframeSequence(animationId)
|
||||
animationObj.AnimationId = KeyframeSequenceProvider:RegisterActiveKeyframeSequence(keyframeSequence)
|
||||
|
||||
for _, track in pairs(humanoid:GetPlayingAnimationTracks()) do
|
||||
track:Stop(0)
|
||||
end
|
||||
|
||||
local track = humanoid:LoadAnimation(animationObj)
|
||||
track:Play(0)
|
||||
|
||||
local finished = false
|
||||
|
||||
local function stepNextFrame()
|
||||
local delta = 1/FRAME_RATE
|
||||
|
||||
if track.TimePosition + delta > track.Length then
|
||||
delta = track.Length - track.TimePosition
|
||||
end
|
||||
|
||||
if delta <= 0.005 or track.TimePosition >= track.Length or finished then
|
||||
return false
|
||||
end
|
||||
|
||||
local preStepTimePosition = track.TimePosition
|
||||
animator:StepAnimations(delta)
|
||||
|
||||
if track.TimePosition <= preStepTimePosition then
|
||||
finished = true
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local math_floor = math.floor
|
||||
local math_sin = math.sin
|
||||
local math_cos = math.cos
|
||||
local math_min = math.min
|
||||
local math_max = math.max
|
||||
local function round(number)
|
||||
return math_floor(number*10000 + 0.5)/10000
|
||||
end
|
||||
|
||||
local function vector3ToTable(vector)
|
||||
return {
|
||||
x = round(vector.X),
|
||||
y = round(vector.Y),
|
||||
z = round(vector.Z)
|
||||
}
|
||||
end
|
||||
|
||||
local animationData = {}
|
||||
|
||||
while stepNextFrame() do
|
||||
local frameAnimationData = {}
|
||||
|
||||
for _, obj in pairs(character:GetChildren()) do
|
||||
if obj:IsA("BasePart") or obj:IsA("Accoutrement") then
|
||||
local part = obj
|
||||
if obj:IsA("Accoutrement") then
|
||||
part = obj:FindFirstChild("Handle")
|
||||
end
|
||||
if part and part.Name ~= "HumanoidRootPart" then
|
||||
local posAndRotation = {}
|
||||
posAndRotation["Position"] = vector3ToTable(part.Position)
|
||||
local axis, angle = part.CFrame:toAxisAngle()
|
||||
local halfAngle = angle/2
|
||||
posAndRotation["Rotation"] = {
|
||||
x = round(math_sin(halfAngle)*axis.X),
|
||||
y = round(math_sin(halfAngle)*axis.Y),
|
||||
z = round(math_sin(halfAngle)*axis.Z),
|
||||
w = round(math_cos(halfAngle))
|
||||
}
|
||||
|
||||
frameAnimationData[obj.Name] = posAndRotation
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(animationData, frameAnimationData)
|
||||
end
|
||||
|
||||
-- Take note of finishing CFrames for the animation
|
||||
-- The CameraResult position will be transformed based on this
|
||||
local animatedPartPositionsMap = {}
|
||||
|
||||
for part, _ in pairs(originalPartsCFramesMap) do
|
||||
animatedPartPositionsMap[part] = part.Position
|
||||
end
|
||||
|
||||
-- Restore original CFrames
|
||||
for motor, origC1 in pairs(originalJointCFramesMap) do
|
||||
motor.C1 = origC1
|
||||
end
|
||||
|
||||
for part, cframe in pairs(originalPartsCFramesMap) do
|
||||
part.Anchored = true
|
||||
part.CFrame = cframe
|
||||
end
|
||||
|
||||
local partsArray = {}
|
||||
for _, obj in pairs(character:GetChildren()) do
|
||||
if obj:IsA("BasePart") or obj:IsA("Accoutrement") then
|
||||
if obj.Name ~= "HumanoidRootPart" then
|
||||
table.insert(partsArray, obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local string_sub = string.sub
|
||||
local string_len = string.len
|
||||
function strEndsWith(str, val)
|
||||
return string_sub(str, -string_len(val)) == val
|
||||
end
|
||||
|
||||
function replaceChar(pos, str, r)
|
||||
return str:sub(1, pos - 1) ..r.. str:sub(pos + 1)
|
||||
end
|
||||
|
||||
game:GetService("Selection"):Set(partsArray)
|
||||
local objsStrOutput = nil
|
||||
objsStrOutput, contentIdsUsed = game:GetService("ThumbnailGenerator"):Click("SplitObjs", 0, 0, true)
|
||||
|
||||
local decodedObjsStrOutput = game:GetService("HttpService"):JSONDecode(objsStrOutput)
|
||||
local partObjsResult = {}
|
||||
local textures = {}
|
||||
local cameraResult = nil
|
||||
|
||||
local totalAABB = {
|
||||
["min"] = {},
|
||||
["max"] = {}
|
||||
}
|
||||
|
||||
local function addToTotalAABB(partName, partAABB)
|
||||
local part = character:FindFirstChild(partName)
|
||||
if part and animatedPartPositionsMap[part] then
|
||||
local currentPosition = part.Position
|
||||
local animatedPosition = animatedPartPositionsMap[part]
|
||||
local offset = animatedPosition - currentPosition
|
||||
|
||||
local minJSON = partAABB["min"]
|
||||
local currentMin = Vector3.new(minJSON.x, minJSON.y, minJSON.z)
|
||||
currentMin = Vector3.new(currentMin.X, currentMin.Y + offset.Y, currentMin.Z)
|
||||
partAABB["min"] = vector3ToTable(currentMin)
|
||||
|
||||
local maxJSON = partAABB["min"]
|
||||
local currentMax = Vector3.new(maxJSON.x, maxJSON.y, maxJSON.z)
|
||||
currentMax = Vector3.new(currentMax.X, currentMax.Y + offset.Y, currentMax.Z)
|
||||
partAABB["min"] = vector3ToTable(currentMax)
|
||||
end
|
||||
|
||||
for xyzkey, val in pairs(partAABB["min"]) do
|
||||
if not totalAABB["min"][xyzkey] then
|
||||
totalAABB["min"][xyzkey] = val
|
||||
else
|
||||
totalAABB["min"][xyzkey] = math_min(totalAABB["min"][xyzkey], val)
|
||||
end
|
||||
end
|
||||
|
||||
for xyzkey, val in pairs(partAABB["max"]) do
|
||||
if not totalAABB["max"][xyzkey] then
|
||||
totalAABB["max"][xyzkey] = val
|
||||
else
|
||||
totalAABB["max"][xyzkey] = math_max(totalAABB["max"][xyzkey], val)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function resolveCameraResult(partName, cameraJSON)
|
||||
local part = character:FindFirstChild(partName)
|
||||
if part and animatedPartPositionsMap[part] then
|
||||
local currentPosition = part.Position
|
||||
local animatedPosition = animatedPartPositionsMap[part]
|
||||
local offset = animatedPosition - currentPosition
|
||||
|
||||
local positionJSON = cameraJSON["position"]
|
||||
local cameraPosition = Vector3.new(positionJSON.x, positionJSON.y, positionJSON.z)
|
||||
cameraPosition = cameraPosition + offset
|
||||
cameraJSON["position"] = vector3ToTable(cameraPosition)
|
||||
end
|
||||
|
||||
if partName == "Head" then
|
||||
cameraResult = cameraJSON
|
||||
elseif cameraResult == nil then -- Fallback if Head doesn't exist for some reason
|
||||
cameraResult = cameraJSON
|
||||
end
|
||||
end
|
||||
|
||||
-- Process the SplitObjs output to consolidate it for the animation output.
|
||||
-- The data for common textures is mapped to the same output in the textures table.
|
||||
-- The Camera and AABB fields are consolidated into global fields.
|
||||
for key, val in pairs(decodedObjsStrOutput) do
|
||||
local decodedPartObj = game:GetService("HttpService"):JSONDecode(val)
|
||||
if decodedPartObj["files"] then
|
||||
local files = decodedPartObj["files"]
|
||||
for fileName, fileInfo in pairs(files) do
|
||||
local fileContent = fileInfo.content
|
||||
if strEndsWith(fileName, ".png") then
|
||||
local newFileName = fileName
|
||||
while textures[newFileName] and textures[newFileName].content ~= fileContent do
|
||||
local charsFromEnd = string_len("Tex.png")
|
||||
local replacePos = string_len(newFileName) - charsFromEnd
|
||||
local newNumber = tostring(tonumber(newFileName:sub(replacePos, replacePos)) + 1)
|
||||
newFileName = replaceChar(replacePos, newFileName, newNumber)
|
||||
end
|
||||
textures[newFileName] = {
|
||||
content = fileContent
|
||||
}
|
||||
files["texture"] = newFileName
|
||||
files[fileName] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Calculate total aabb
|
||||
if decodedPartObj["AABB"] then
|
||||
addToTotalAABB(key, decodedPartObj["AABB"])
|
||||
decodedPartObj["AABB"] = nil
|
||||
end
|
||||
|
||||
-- Resolve overall Camera JSON.
|
||||
if decodedPartObj["camera"] then
|
||||
resolveCameraResult(key, decodedPartObj["camera"])
|
||||
decodedPartObj["camera"] = nil
|
||||
end
|
||||
partObjsResult[key] = decodedPartObj
|
||||
end
|
||||
|
||||
-- Special camera position and direction for rotated character
|
||||
if rotateCharacter then
|
||||
local rootPart = character:FindFirstChild("HumanoidRootPart")
|
||||
if rootPart then
|
||||
local cameraOffset = Vector3.new(6, 5, 7) * .7
|
||||
local cameraPosition = Vector3.new(rootPart.Position.X, rootPart.Position.Y, rootPart.Position.Z) - cameraOffset
|
||||
local cameraDirection = (rootPart.Position - cameraPosition).unit
|
||||
cameraResult["position"] = vector3ToTable(cameraPosition)
|
||||
cameraResult["direction"] = vector3ToTable(cameraDirection)
|
||||
end
|
||||
end
|
||||
|
||||
local resultData = {
|
||||
Frames = animationData,
|
||||
Camera = cameraResult,
|
||||
AABB = totalAABB,
|
||||
PartObjs = partObjsResult,
|
||||
Textures = textures
|
||||
}
|
||||
|
||||
return game:GetService("HttpService"):JSONEncode(resultData), contentIdsUsed
|
||||
@@ -0,0 +1,24 @@
|
||||
-- Avatar v1.0.1
|
||||
-- This is the thumbnail script for R6 avatars. Straight up and down, with the right arm out if they have a gear.
|
||||
|
||||
characterAppearanceUrl, baseUrl, fileExtension, x, y = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
print(characterAppearanceUrl)
|
||||
player:LoadCharacterBlocking()
|
||||
|
||||
-- Raise up the character's arm if they have gear.
|
||||
if player.Character then
|
||||
for _, child in pairs(player.Character:GetChildren()) do
|
||||
if child:IsA("Tool") then
|
||||
player.Character.Torso["Right Shoulder"].CurrentAngle = math.rad(90)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,120 @@
|
||||
-- AvatarAnimation V 1.0.3
|
||||
-- Creates a thumbnail from the middle Keyframe of an animation.
|
||||
|
||||
-- Example arguments:
|
||||
-- https://www.sitetest3.robloxlabs.com/Asset/AvatarAccoutrements.ashx?AvatarHash=80dcfa39a8e90f690d2be0e56239abbe&AssetIDs=42900214,32357663,32357631,32357619,32357584,32357558&ResolvedAvatarType=R15,
|
||||
-- http://www.sitetest3.robloxlabs.com/,
|
||||
-- Png,
|
||||
-- 600,
|
||||
-- 600,
|
||||
-- http://www.sitetest3.robloxlabs.com/Asset/?hash=ca005a9e83ac95bd5068029afdc65496
|
||||
|
||||
characterAppearanceUrl, baseUrl, fileExtension, x, y, animationUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
|
||||
local function getJointBetween(part0, part1)
|
||||
local foundJoint = nil
|
||||
for _, obj in pairs(part1:GetChildren()) do
|
||||
if obj:IsA("Motor6D") and obj.Part0 == part0 then
|
||||
return obj
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function applyR15Pose(character, poseKeyframe)
|
||||
local function recurApplyPoses(parentPose, poseObject)
|
||||
if parentPose then
|
||||
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
|
||||
joint.C1 = joint.C1 * poseObject.CFrame:inverse()
|
||||
end
|
||||
for _, subPose in pairs(poseObject:GetSubPoses()) do
|
||||
recurApplyPoses(poseObject, subPose)
|
||||
end
|
||||
end
|
||||
|
||||
for _, poseObj in pairs(poseKeyframe:GetPoses()) do
|
||||
recurApplyPoses(nil, poseObj)
|
||||
end
|
||||
end
|
||||
|
||||
local animationObjects = game:GetObjects(animationUrl)
|
||||
|
||||
local poseAnimation = nil
|
||||
local animations = {}
|
||||
local rotateCharacter = false
|
||||
local thumbnailCamera = nil
|
||||
|
||||
local function getAnimations(model)
|
||||
for _, child in pairs(model:GetChildren()) do
|
||||
if child:IsA("Animation") then
|
||||
if string.lower(model.Name) == "pose" then
|
||||
poseAnimation = child
|
||||
else
|
||||
table.insert(animations, child)
|
||||
end
|
||||
else
|
||||
getAnimations(child)
|
||||
end
|
||||
|
||||
if child:IsA("Camera") and child.Name == "ThumbnailCamera" then
|
||||
thumbnailCamera = child:Clone()
|
||||
end
|
||||
|
||||
if child:IsA("StringValue") and child.Name == "swim" then
|
||||
rotateCharacter = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, animationModel in pairs(animationObjects) do
|
||||
getAnimations(animationModel)
|
||||
end
|
||||
|
||||
local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider")
|
||||
local keyframes = {}
|
||||
|
||||
if poseAnimation then
|
||||
local kfs = KeyframeSequenceProvider:GetKeyframeSequence(poseAnimation.AnimationId)
|
||||
local animKeyframes = kfs:GetKeyframes()
|
||||
for _, keyframe in pairs(animKeyframes) do
|
||||
table.insert(keyframes, keyframe)
|
||||
end
|
||||
else
|
||||
for _, animation in pairs(animations) do
|
||||
local kfs = KeyframeSequenceProvider:GetKeyframeSequence(animation.AnimationId)
|
||||
local animKeyframes = kfs:GetKeyframes()
|
||||
for _, keyframe in pairs(animKeyframes) do
|
||||
table.insert(keyframes, keyframe)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local keyframe = keyframes[math.max(1, math.floor(#keyframes/2))]
|
||||
applyR15Pose(player.Character, keyframe)
|
||||
|
||||
if rotateCharacter then
|
||||
local rootPart = player.Character:FindFirstChild("HumanoidRootPart")
|
||||
if rootPart then
|
||||
rootPart.CFrame = rootPart.CFrame * CFrame.Angles(math.rad(-90), 0, 0)
|
||||
if not thumbnailCamera then
|
||||
local camera = Instance.new("Camera")
|
||||
camera.Name = "ThumbnailCamera"
|
||||
|
||||
local rotatedCameraOffset = Vector3.new(6, 5, 7) * .7
|
||||
camera.CFrame = CFrame.new(Vector3.new(rootPart.Position.X, rootPart.Position.Y, rootPart.Position.Z) - rotatedCameraOffset, rootPart.Position)
|
||||
camera.Parent = player.Character
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if thumbnailCamera then
|
||||
thumbnailCamera.Parent = player.Character
|
||||
end
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,164 @@
|
||||
-- Avatar_R15_Action v1.1.0
|
||||
-- For R6, this generates the normal with/without gear pose. For R15 it positions their body in an action pose.
|
||||
baseUrl, characterAppearanceUrl, fileExtension, x, y = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
|
||||
local poseAnimationId = "http://www.roblox.com/asset/?id=532421348"
|
||||
|
||||
local function getJointBetween(part0, part1)
|
||||
for _, obj in pairs(part1:GetChildren()) do
|
||||
if obj:IsA("Motor6D") and obj.Part0 == part0 then
|
||||
return obj
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function applyKeyframe(character, poseKeyframe)
|
||||
local function recurApplyPoses(parentPose, poseObject)
|
||||
if parentPose then
|
||||
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
|
||||
if joint and poseObject.Weight ~= 0 then
|
||||
joint.C1 = poseObject.CFrame:inverse() + joint.C1.p
|
||||
end
|
||||
end
|
||||
for _, subPose in pairs(poseObject:GetSubPoses()) do
|
||||
recurApplyPoses(poseObject, subPose)
|
||||
end
|
||||
end
|
||||
|
||||
for _, poseObj in pairs(poseKeyframe:GetPoses()) do
|
||||
recurApplyPoses(nil, poseObj)
|
||||
end
|
||||
end
|
||||
|
||||
local function applyR15Pose(character)
|
||||
local poseKeyframSequence = game:GetService("KeyframeSequenceProvider"):GetKeyframeSequence(poseAnimationId)
|
||||
local poseKeyframe = poseKeyframSequence:GetKeyframes()[1]
|
||||
|
||||
applyKeyframe(character, poseKeyframe)
|
||||
end
|
||||
|
||||
local function findAttachmentsRecur(parent, resultTable, returnDictionary)
|
||||
for _, obj in pairs(parent:GetChildren()) do
|
||||
if obj:IsA("Attachment") then
|
||||
if returnDictionary then
|
||||
resultTable[obj.Name] = obj
|
||||
else
|
||||
resultTable[#resultTable + 1] = obj
|
||||
end
|
||||
elseif not obj:IsA("Tool") and not obj:IsA("Accoutrement") then -- Leave out tools and accoutrements in the character
|
||||
findAttachmentsRecur(obj, resultTable, returnDictionary)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function findAttachmentsInTool(tool)
|
||||
local attachments = {}
|
||||
findAttachmentsRecur(tool, attachments, false)
|
||||
return attachments
|
||||
end
|
||||
|
||||
local function findAttachmentsInCharacter(character)
|
||||
local attachments = {}
|
||||
findAttachmentsRecur(character, attachments, true)
|
||||
return attachments
|
||||
end
|
||||
|
||||
local function weldAttachments(attach1, attach2)
|
||||
local weld = Instance.new("Weld")
|
||||
weld.Part0 = attach1.Parent
|
||||
weld.Part1 = attach2.Parent
|
||||
weld.C0 = attach1.CFrame
|
||||
weld.C1 = attach2.CFrame
|
||||
weld.Parent = attach1.Parent
|
||||
return weld
|
||||
end
|
||||
|
||||
function findFirstMatchingAttachment(model, name)
|
||||
for _, child in pairs(model:GetChildren()) do
|
||||
if child:IsA("Attachment") and child.Name == name then
|
||||
return child
|
||||
elseif not child:IsA("Accoutrement") and not child:IsA("Tool") then
|
||||
local foundAttachment = findFirstMatchingAttachment(child, name)
|
||||
if foundAttachment then
|
||||
return foundAttachment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function doR15ToolPose(character, humanoid, tool)
|
||||
local characterAttachments = findAttachmentsInCharacter(character)
|
||||
local toolAttachments = findAttachmentsInTool(tool)
|
||||
local foundAttachments = false
|
||||
-- If matching attachments exist in the gear then weld them and do the "action" R15 pose.
|
||||
-- Otherwise keep the R15 in the T-Pose position and just raise the arm.
|
||||
for _, attachment in pairs(toolAttachments) do
|
||||
local matchingAttachment = characterAttachments[attachment.Name]
|
||||
if matchingAttachment then
|
||||
foundAttachments = true
|
||||
weldAttachments(matchingAttachment, attachment)
|
||||
end
|
||||
end
|
||||
|
||||
if foundAttachments then
|
||||
tool.Parent = character
|
||||
applyR15Pose(character)
|
||||
|
||||
local toolPose = tool:FindFirstChild("ThumbnailPose")
|
||||
if toolPose and toolPose:IsA("Keyframe") then
|
||||
applyKeyframe(character, toolPose)
|
||||
end
|
||||
else
|
||||
tool.Parent = nil
|
||||
local rightShoulderJoint = getJointBetween(character.UpperTorso, character.RightUpperArm)
|
||||
if rightShoulderJoint then
|
||||
rightShoulderJoint.C1 = rightShoulderJoint.C1 * CFrame.new(0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 1, 0):inverse()
|
||||
end
|
||||
if tool:FindFirstChild("Handle") then
|
||||
local attachment = findFirstMatchingAttachment(character, "RightGripAttachment")
|
||||
if attachment then
|
||||
tool.Handle.CFrame = attachment.Parent.CFrame * attachment.CFrame * tool.Grip:inverse()
|
||||
end
|
||||
end
|
||||
humanoid:EquipTool(tool)
|
||||
end
|
||||
end
|
||||
|
||||
local character = player.Character
|
||||
if character then
|
||||
local tool = character:FindFirstChildOfClass("Tool")
|
||||
local humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
local animateScript = character:FindFirstChild("Animate")
|
||||
if animateScript then
|
||||
local equippedPoseValue = animateScript:FindFirstChild("Pose") or animateScript:FindFirstChild("pose")
|
||||
if equippedPoseValue then
|
||||
local poseAnim = equippedPoseValue:FindFirstChildOfClass("Animation")
|
||||
if poseAnim then
|
||||
poseAnimationId = poseAnim.AnimationId
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if humanoid then
|
||||
if humanoid.RigType == Enum.HumanoidRigType.R6 then
|
||||
if tool then
|
||||
character.Torso["Right Shoulder"].CurrentAngle = math.rad(90)
|
||||
end
|
||||
elseif humanoid.RigType == Enum.HumanoidRigType.R15 then
|
||||
if tool then
|
||||
doR15ToolPose(character, humanoid, tool)
|
||||
else
|
||||
applyR15Pose(character)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,43 @@
|
||||
--Avatar_R15_Standard v1.0.1
|
||||
--Pose R6 characters in the normal way. For R15, have them in the same pose, and raise their arm up if they have gear.
|
||||
|
||||
baseUrl, characterAppearanceUrl, fileExtension, x, y = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
|
||||
local function getJointBetween(part0, part1)
|
||||
for _, obj in pairs(part1:GetChildren()) do
|
||||
if obj:IsA("Motor6D") and obj.Part0 == part0 then
|
||||
return obj
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function doR15ToolPose(rig)
|
||||
local rightShoulderJoint = getJointBetween(rig.UpperTorso, rig.RightUpperArm)
|
||||
if rightShoulderJoint then
|
||||
rightShoulderJoint.C1 = rightShoulderJoint.C1 * CFrame.new(0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 1, 0):inverse()
|
||||
end
|
||||
end
|
||||
|
||||
-- Raise right arm up to hold gear.
|
||||
local character = player.Character
|
||||
if character then
|
||||
if character:FindFirstChildOfClass("Tool") then
|
||||
local humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
if humanoid then
|
||||
if humanoid.RigType == Enum.HumanoidRigType.R6 then
|
||||
character.Torso['Right Shoulder'].CurrentAngle = math.rad(90)
|
||||
elseif humanoid.RigType == Enum.HumanoidRigType.R15 then
|
||||
doR15ToolPose(character)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return game:GetService('ThumbnailGenerator'):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,157 @@
|
||||
-- BodyPart v1.0.5
|
||||
-- See http://wiki.roblox.com/index.php?title=R15_Compatibility_Guide#Package_Parts for details on how body parts work with R15
|
||||
|
||||
local assetUrl, baseUrl, fileExtension, x, y, R6RigUrl = ...
|
||||
|
||||
pcall(function() game:GetService('ContentProvider'):SetBaseUrl(baseUrl) end)
|
||||
game:GetService('ScriptContext').ScriptsDisabled = true
|
||||
|
||||
local objects = game:GetObjects(assetUrl)
|
||||
local useR15 = false
|
||||
local useR15NewNames = false
|
||||
local bodyPartProportion = "Classic"
|
||||
|
||||
for _, object in pairs(objects) do
|
||||
if object:IsA("Folder") and (object.Name == "R15" or object.Name == "R15ArtistIntent") then
|
||||
useR15 = true
|
||||
|
||||
-- Check to see if there are native scale parts in this object
|
||||
local partScaleType = object:FindFirstChild("AvatarPartScaleType", true)
|
||||
if partScaleType then
|
||||
bodyPartProportion = partScaleType.Value
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local R15RigUrl = "http://www.roblox.com/asset/?id=516159357"
|
||||
for _, object in pairs(objects) do
|
||||
if object:IsA("Folder") and object.Name == "R15ArtistIntent" then
|
||||
useR15NewNames = true
|
||||
if bodyPartProportion == "Classic" then
|
||||
R15RigUrl = "http://www.roblox.com/asset/?id=1664543044"
|
||||
else
|
||||
R15RigUrl = "http://www.roblox.com/asset/?id=2337256345"
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local mannequin = nil
|
||||
if useR15 then
|
||||
mannequin = game:GetObjects(R15RigUrl)[1]
|
||||
else
|
||||
mannequin = game:GetObjects(R6RigUrl)[1]
|
||||
end
|
||||
|
||||
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
||||
mannequin.Parent = workspace
|
||||
|
||||
--game:GetObjects(customUrl)[1].Parent = mannequin
|
||||
|
||||
function addFolderChildren(folder)
|
||||
for _, child in pairs(folder:GetChildren()) do
|
||||
local existingBodyPart = mannequin:FindFirstChild(child.Name)
|
||||
if existingBodyPart then
|
||||
existingBodyPart:Destroy()
|
||||
end
|
||||
child.Parent = mannequin
|
||||
end
|
||||
end
|
||||
|
||||
local r15FolderName = "R15"
|
||||
if (useR15 and useR15NewNames) then
|
||||
r15FolderName = "R15ArtistIntent"
|
||||
end
|
||||
|
||||
for _, object in pairs(objects) do
|
||||
if useR15 and object:IsA("Folder") and object.Name == r15FolderName then
|
||||
addFolderChildren(object)
|
||||
elseif not useR15 and object:IsA("Folder") and object.Name == "R6" then
|
||||
addFolderChildren(object)
|
||||
elseif not (object:IsA("Folder") and string.find(object.Name, "R15")) then -- There will now be MULTIPLE R15 Folders. Ignore the ones we didn't search for.
|
||||
object.Parent = mannequin
|
||||
end
|
||||
end
|
||||
|
||||
local function buildJoint(parentAttachment, partForJointAttachment)
|
||||
local jointName = parentAttachment.Name:gsub("RigAttachment", "")
|
||||
local motor = partForJointAttachment.Parent:FindFirstChild(jointName)
|
||||
if not motor then
|
||||
motor = Instance.new("Motor6D")
|
||||
end
|
||||
motor.Name = jointName
|
||||
|
||||
motor.Part0 = parentAttachment.Parent
|
||||
motor.Part1 = partForJointAttachment.Parent
|
||||
|
||||
motor.C0 = parentAttachment.CFrame
|
||||
motor.C1 = partForJointAttachment.CFrame
|
||||
|
||||
motor.Parent = partForJointAttachment.Parent
|
||||
end
|
||||
|
||||
-- Builds an R15 rig from the attachments in the parts
|
||||
function buildRigFromAttachments(currentPart, lastPart)
|
||||
local validSiblings = {}
|
||||
for _, sibling in pairs(currentPart.Parent:GetChildren()) do
|
||||
-- Don't find matching attachment in the current part being processed.
|
||||
-- Don't visit the last part visited again, this would cause an infinite loop.
|
||||
if sibling:IsA("BasePart") and sibling ~= currentPart and sibling ~= lastPart then
|
||||
table.insert(validSiblings, sibling)
|
||||
end
|
||||
end
|
||||
|
||||
local function processRigAttachment(attachment)
|
||||
for _, sibling in pairs(validSiblings) do
|
||||
local matchingAttachment = sibling:FindFirstChild(attachment.Name)
|
||||
if matchingAttachment then
|
||||
buildJoint(attachment, matchingAttachment)
|
||||
buildRigFromAttachments(matchingAttachment.Parent, currentPart)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, object in pairs(currentPart:GetChildren()) do
|
||||
if object:IsA("Attachment") and string.find(object.Name, "RigAttachment") then
|
||||
processRigAttachment(object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if useR15 then
|
||||
-- Build R15 rig
|
||||
local humanoidRootPart = mannequin:WaitForChild("HumanoidRootPart")
|
||||
humanoidRootPart.CFrame = CFrame.new(Vector3.new(0, 5, 0)) * CFrame.Angles(0, math.pi, 0)
|
||||
humanoidRootPart.Anchored = true
|
||||
buildRigFromAttachments(humanoidRootPart)
|
||||
local humanoid = mannequin:WaitForChild("Humanoid")
|
||||
if humanoid then
|
||||
local typeObject = humanoid:FindFirstChild("BodyTypeScale")
|
||||
|
||||
if typeObject == nil then
|
||||
typeObject = Instance.new("NumberValue")
|
||||
typeObject.Name = "BodyTypeScale"
|
||||
typeObject.Value = 0
|
||||
typeObject.Parent = humanoid
|
||||
end
|
||||
|
||||
local proportionObject = humanoid:FindFirstChild("BodyProportionScale")
|
||||
if proportionObject == nil then
|
||||
proportionObject = Instance.new("NumberValue")
|
||||
proportionObject.Name = "BodyProportionScale"
|
||||
proportionObject.Value = 0
|
||||
proportionObject.Parent = humanoid
|
||||
end
|
||||
|
||||
if bodyPartProportion == "ProportionsNormal" then
|
||||
typeObject.Value = 1
|
||||
proportionObject.Value = 0
|
||||
elseif bodyPartProportion == "ProportionsSlender" then
|
||||
typeObject.Value = 1
|
||||
proportionObject.Value = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,47 @@
|
||||
-- Closeup v1.0.3
|
||||
-- Used for avatar closeup
|
||||
baseUrl, characterAppearanceUrl, fileExtension, x, y, quadratic, baseHatZoom, maxHatZoom, cameraOffsetX, cameraOffsetY = ...
|
||||
|
||||
pcall(function() game:GetService('ContentProvider'):SetBaseUrl(baseUrl) end)
|
||||
game:GetService('ScriptContext').ScriptsDisabled = true
|
||||
|
||||
local player = game:GetService("Players"):CreateLocalPlayer(0)
|
||||
player.CharacterAppearance = characterAppearanceUrl
|
||||
player:LoadCharacterBlocking()
|
||||
|
||||
local maxDimension = 0
|
||||
|
||||
if player.Character then
|
||||
-- Remove gear
|
||||
for _, child in pairs(player.Character:GetChildren()) do
|
||||
if child:IsA("Tool") then
|
||||
child:Destroy()
|
||||
elseif child:IsA("Accoutrement") then
|
||||
local size = child.Handle.Size / 2 + child.Handle.Position - player.Character.Head.Position
|
||||
local xy = Vector2.new(size.x, size.y)
|
||||
if xy.magnitude > maxDimension then
|
||||
maxDimension = xy.magnitude
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Setup Camera
|
||||
local maxHatOffset = 0.5 -- Maximum amount to move camera upward to accomodate large hats
|
||||
maxDimension = math.min(1, maxDimension / 3) -- Confine maxdimension to specific bounds
|
||||
|
||||
if quadratic then
|
||||
maxDimension = maxDimension * maxDimension -- Zoom out on quadratic interpolation
|
||||
end
|
||||
|
||||
local viewOffset = player.Character.Head.CFrame * CFrame.new(cameraOffsetX, cameraOffsetY + maxHatOffset * maxDimension, 0.1) -- View vector offset from head
|
||||
local positionOffset = player.Character.Head.CFrame + (CFrame.Angles(0, -math.pi / 16, 0).lookVector.unit * 3) -- Position vector offset from head
|
||||
|
||||
local camera = Instance.new("Camera", player.Character)
|
||||
camera.Name = "ThumbnailCamera"
|
||||
camera.CameraType = Enum.CameraType.Scriptable
|
||||
camera.CoordinateFrame = CFrame.new(positionOffset.p, viewOffset.p)
|
||||
camera.FieldOfView = baseHatZoom + (maxHatZoom - baseHatZoom) * maxDimension
|
||||
workspace.CurrentCamera = camera -- I added this
|
||||
end
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,23 @@
|
||||
-- Decal v1.0.2
|
||||
-- Used for faces and decals
|
||||
|
||||
assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService('ScriptContext').ScriptsDisabled = true
|
||||
|
||||
local decal = game:GetObjects(assetUrl)[1]
|
||||
|
||||
local thumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
|
||||
local image, requestedUrls
|
||||
local success = pcall(function()
|
||||
image, requestedUrls = thumbnailGenerator:ClickTexture(decal.Texture, fileExtension, x, y)
|
||||
end)
|
||||
|
||||
if success then
|
||||
return image, requestedUrls
|
||||
end
|
||||
|
||||
-- if we fail return the hourglass, since we're probably in moderation.
|
||||
return "/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wgARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAX8P/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABBQJ//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAGPwJ//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIAAwAAABAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPxB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPxB//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxB//9k=", {decal.Texture}
|
||||
@@ -0,0 +1,17 @@
|
||||
-- Gear v1.0.2
|
||||
|
||||
assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
for _, object in pairs(game:GetObjects(assetUrl)) do
|
||||
object.Parent = workspace
|
||||
end
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
if string.lower(fileExtension) == "obj" then
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop =]] true)
|
||||
end
|
||||
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop =]] true)
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Hat v1.0.1
|
||||
|
||||
assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
game:GetObjects(assetUrl)[1].Parent = workspace
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
if string.lower(fileExtension) == "obj" then
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop = ]] true)
|
||||
end
|
||||
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop = ]] true)
|
||||
@@ -0,0 +1,27 @@
|
||||
-- Head v1.0.1
|
||||
|
||||
assetUrl, fileExtension, x, y, baseUrl, mannequinId = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local mannequin = game:GetObjects(baseUrl.. "/asset/?id=" .. tostring(mannequinId))[1]
|
||||
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
||||
mannequin.Parent = workspace
|
||||
|
||||
mannequin.Head.BrickColor = BrickColor.Gray()
|
||||
if mannequin.Head:FindFirstChild("Mesh") then
|
||||
mannequin.Head.Mesh:Destroy()
|
||||
end
|
||||
|
||||
for _, child in pairs(mannequin:GetChildren()) do
|
||||
if child:IsA("BasePart") and child.Name ~= "Head" then
|
||||
child:Destroy()
|
||||
end
|
||||
end
|
||||
|
||||
local mesh = game:GetObjects(assetUrl)[1]
|
||||
mesh.Parent = mannequin.Head
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,36 @@
|
||||
imageId, baseUrl, fileExtension, x, y = ...
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
game:GetService('StarterGui'):SetCoreGuiEnabled(Enum.CoreGuiType.All, false);
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local IsSuccess, actualImageId = pcall(function()
|
||||
local Asset = game:GetObjects(baseUrl.."/asset/?id="..tostring(imageId))[1]
|
||||
if Asset:IsA("Decal") then
|
||||
return Asset.Texture
|
||||
end
|
||||
for _, child in pairs(Asset:GetDescendants()) do
|
||||
if child:IsA("Decal") then
|
||||
return child.Texture
|
||||
end
|
||||
end
|
||||
end)
|
||||
local AssetURL
|
||||
if not IsSuccess then
|
||||
actualImageId = imageId
|
||||
AssetURL = baseUrl.."/asset/?id="..tostring(imageId)
|
||||
else
|
||||
AssetURL = actualImageId
|
||||
end
|
||||
|
||||
local success, result = pcall(function()
|
||||
return ThumbnailGenerator:ClickTexture(AssetURL, fileExtension, x, y)
|
||||
end)
|
||||
if not success then
|
||||
result = "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAQAAABecRxxAAAEnklEQVR42u3UMQEAAAjDMOZf9JCAABIJPZp2gKdiAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYABiAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAAIoABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYABiAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAGAABgAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAYABiAAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAEABgAYAGAAgAGAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAQAGABgAYACAAYABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABgAIABAAYAGABwW1Dy/i5f+lAuAAAAAElFTkSuQmCC" -- 1x1 transparent pixel
|
||||
end
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Mesh v1.0.1
|
||||
|
||||
assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local part = Instance.new("Part")
|
||||
part.Parent = workspace
|
||||
|
||||
local specialMesh = Instance.new("SpecialMesh")
|
||||
specialMesh.MeshId = assetUrl
|
||||
specialMesh.Parent = part
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop = ]] true)
|
||||
@@ -0,0 +1,32 @@
|
||||
-- MeshPart v1.0.1
|
||||
|
||||
assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
|
||||
for _, object in pairs(game:GetObjects(assetUrl)) do
|
||||
if object:IsA("Sky") then
|
||||
local resultValues = nil
|
||||
local success = pcall(function() resultValues = {ThumbnailGenerator:ClickTexture(object.SkyboxFt, fileExtension, x, y)} end)
|
||||
if success then
|
||||
return unpack(resultValues)
|
||||
else
|
||||
object.Parent = game:GetService("Lighting")
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] false)
|
||||
end
|
||||
elseif object:IsA("LuaSourceContainer") then
|
||||
return ThumbnailGenerator:ClickTexture(baseUrl.. "/static/img/LuaThumbnail.png", fileExtension, x, y)
|
||||
elseif object:IsA("SpecialMesh") then
|
||||
local part = Instance.new("Part")
|
||||
part.Parent = workspace
|
||||
object.Parent = part
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
else
|
||||
pcall(function() object.Parent = workspace end)
|
||||
end
|
||||
end
|
||||
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,32 @@
|
||||
-- Model v1.0.1
|
||||
|
||||
assetUrl, fileExtension, x, y, baseUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local ThumbnailGenerator = game:GetService("ThumbnailGenerator")
|
||||
|
||||
for _, object in pairs(game:GetObjects(assetUrl)) do
|
||||
if object:IsA("Sky") then
|
||||
local resultValues = nil
|
||||
local success = pcall(function() resultValues = {ThumbnailGenerator:ClickTexture(object.SkyboxFt, fileExtension, x, y)} end)
|
||||
if success then
|
||||
return unpack(resultValues)
|
||||
else
|
||||
object.Parent = game:GetService("Lighting")
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] false)
|
||||
end
|
||||
--elseif object:IsA("LuaSourceContainer") then
|
||||
--return ThumbnailGenerator:ClickTexture(baseUrl.. "/static/img/LuaThumbnail.png", fileExtension, x, y)
|
||||
elseif object:IsA("SpecialMesh") then
|
||||
local part = Instance.new("Part")
|
||||
part.Parent = workspace
|
||||
object.Parent = part
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
else
|
||||
pcall(function() object.Parent = workspace end)
|
||||
end
|
||||
end
|
||||
|
||||
return ThumbnailGenerator:Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,349 @@
|
||||
-- Package v1.1.6
|
||||
-- See http://wiki.roblox.com/index.php?title=R15_Compatibility_Guide#Package_Parts for details on how body parts work with R15
|
||||
|
||||
local assetUrls, baseUrl, fileExtension, x, y, R6RigUrl, customTextureUrls = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
function split(str, delim)
|
||||
local results = {}
|
||||
local lastMatchEnd = 0
|
||||
|
||||
local matchStart, matchEnd = string.find(str, delim, --[[init = ]] 1, --[[plain = ]] true)
|
||||
while matchStart and matchEnd do
|
||||
if matchStart - lastMatchEnd > 1 then
|
||||
table.insert(results, string.sub(str, lastMatchEnd + 1, matchStart - 1))
|
||||
end
|
||||
|
||||
lastMatchEnd = matchEnd
|
||||
matchStart, matchEnd = string.find(str, delim, --[[init = ]] lastMatchEnd + 1, --[[plain = ]] true)
|
||||
end
|
||||
|
||||
if string.len(str) - lastMatchEnd > 1 then
|
||||
table.insert(results, string.sub(str, lastMatchEnd + 1))
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
local R15ArtistIntentAssets = {}
|
||||
local R15Assets = {}
|
||||
local R6Assets = {}
|
||||
local bothAssets = {}
|
||||
|
||||
local useR15ArtistIntent = false
|
||||
local useR15 = true
|
||||
local poseAnimationId = nil
|
||||
|
||||
local poseValueFound = false
|
||||
local function processR15Anim(animFolder)
|
||||
local function processStrValue(strValue)
|
||||
local animation = strValue:FindFirstChildOfClass("Animation")
|
||||
if not animation then
|
||||
return
|
||||
end
|
||||
|
||||
-- By default the pose animation will be used for the thumbnail
|
||||
-- If the pose animation doesn't exist then the idle will be used, otherwise the first animation will be used.
|
||||
if string.lower(strValue.Name) == "pose" then
|
||||
poseValueFound = true
|
||||
poseAnimationId = animation.AnimationId
|
||||
elseif not poseValueFound and string.lower(strValue.Name) == "idle" then
|
||||
poseAnimationId = animation.AnimationId
|
||||
elseif not poseAnimationId then
|
||||
poseAnimationId = animation.AnimationId
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for _, obj in pairs(animFolder:GetChildren()) do
|
||||
if obj:IsA("StringValue") then
|
||||
processStrValue(obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local assetUrlsList = split(assetUrls, ";")
|
||||
|
||||
for _, assetUrl in pairs(assetUrlsList) do
|
||||
local currObjects = game:GetObjects(assetUrl)
|
||||
|
||||
for _,object in pairs(currObjects) do
|
||||
if object:IsA("Folder") and object.Name == "R15ArtistIntent" then
|
||||
for _, child in pairs(object:GetChildren()) do
|
||||
table.insert(R15ArtistIntentAssets, child)
|
||||
end
|
||||
elseif object:IsA("Folder") and object.Name == "R15Fixed" then
|
||||
-- Do nothing. We just don't want this to be dumped in bothAssets
|
||||
elseif object:IsA("Folder") and object.Name == "R15" then
|
||||
for _, child in pairs(object:GetChildren()) do
|
||||
table.insert(R15Assets, child)
|
||||
end
|
||||
elseif object:IsA("Folder") and object.Name == "R6" then
|
||||
foundR6CompatibleParts = true
|
||||
for _, child in pairs(object:GetChildren()) do
|
||||
table.insert(R6Assets, child)
|
||||
end
|
||||
elseif object:IsA("CharacterMesh") then
|
||||
-- Legacy body part format using a CharacterMesh
|
||||
table.insert(R6Assets, object)
|
||||
elseif object:IsA("Folder") and object.Name == "R15Anim" then
|
||||
processR15Anim(object)
|
||||
else
|
||||
table.insert(bothAssets, object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- if the package doesn't contain animations, use this pose.
|
||||
poseAnimationId = poseAnimationId or "http://www.roblox.com/asset/?id=532421348"
|
||||
|
||||
-- Only use R6 if we found body parts that are only compatible with R15
|
||||
if #R6Assets ~= 0 and #R15Assets == 0 and #R15ArtistIntentAssets == 0 then
|
||||
useR15 = false
|
||||
end
|
||||
|
||||
local R15RigUrl = "http://www.roblox.com/asset/?id=516159357"
|
||||
if useR15 and #R15ArtistIntentAssets > 0 then
|
||||
useR15ArtistIntent = true
|
||||
R15RigUrl = "http://www.roblox.com/asset/?id=1664543044"
|
||||
end
|
||||
|
||||
local mannequin = nil
|
||||
if useR15 then
|
||||
mannequin = game:GetObjects(R15RigUrl)[1]
|
||||
if (useR15ArtistIntent) then
|
||||
for _, obj in pairs(R15ArtistIntentAssets) do
|
||||
table.insert(bothAssets, obj)
|
||||
end
|
||||
else
|
||||
for _,obj in pairs(R15Assets) do
|
||||
table.insert(bothAssets, obj)
|
||||
end
|
||||
end
|
||||
else
|
||||
mannequin = game:GetObjects(R6RigUrl)[1]
|
||||
for _,obj in pairs(R6Assets) do
|
||||
table.insert(bothAssets, obj)
|
||||
end
|
||||
end
|
||||
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
||||
mannequin.Parent = workspace
|
||||
|
||||
local tool = nil
|
||||
local accoutrements = {}
|
||||
|
||||
for _, currObject in pairs(bothAssets) do
|
||||
if currObject:IsA("BasePart") then
|
||||
local existingBodyPart = mannequin:FindFirstChild(currObject.Name)
|
||||
if existingBodyPart ~= nil then
|
||||
existingBodyPart:Destroy()
|
||||
end
|
||||
end
|
||||
|
||||
if currObject:IsA("Tool") then
|
||||
if useR15 then
|
||||
tool = currObject
|
||||
else
|
||||
mannequin.Torso["Right Shoulder"].CurrentAngle = math.rad(90)
|
||||
currObject.Parent = mannequin
|
||||
end
|
||||
elseif currObject:IsA("DataModelMesh") then
|
||||
local headMesh = mannequin.Head:FindFirstChild("Mesh")
|
||||
if headMesh then
|
||||
headMesh:Destroy()
|
||||
end
|
||||
currObject.Parent = mannequin.Head
|
||||
elseif currObject:IsA("Decal") then
|
||||
local face = mannequin.Head:FindFirstChild("face")
|
||||
if face then
|
||||
face:Destroy()
|
||||
end
|
||||
currObject.Parent = mannequin.Head
|
||||
elseif currObject:IsA("Accoutrement") then
|
||||
table.insert(accoutrements, currObject)
|
||||
else
|
||||
currObject.Parent = mannequin
|
||||
end
|
||||
end
|
||||
|
||||
local textureUrls = split(customTextureUrls, ";")
|
||||
for _, url in pairs(textureUrls) do
|
||||
local obj = game:GetObjects(url)[1]
|
||||
if obj:IsA("Shirt") then
|
||||
-- Don't add a texture Shirt if package already has a Shirt
|
||||
if not mannequin:FindFirstChildOfClass("Shirt") then
|
||||
obj.Parent = mannequin
|
||||
end
|
||||
elseif obj:IsA("Pants") then
|
||||
-- Don't add a texture Pants if package already has a Pants
|
||||
if not mannequin:FindFirstChildOfClass("Pants") then
|
||||
obj.Parent = mannequin
|
||||
end
|
||||
else
|
||||
obj.Parent = mannequin
|
||||
end
|
||||
end
|
||||
|
||||
local function buildJoint(parentAttachment, partForJointAttachment)
|
||||
local jointName = parentAttachment.Name:gsub("RigAttachment", "")
|
||||
local motor = partForJointAttachment.Parent:FindFirstChild(jointName)
|
||||
if not motor then
|
||||
motor = Instance.new("Motor6D")
|
||||
end
|
||||
motor.Name = jointName
|
||||
|
||||
motor.Part0 = parentAttachment.Parent
|
||||
motor.Part1 = partForJointAttachment.Parent
|
||||
|
||||
motor.C0 = parentAttachment.CFrame
|
||||
motor.C1 = partForJointAttachment.CFrame
|
||||
|
||||
motor.Parent = partForJointAttachment.Parent
|
||||
end
|
||||
|
||||
-- Builds an R15 rig from the attachments in the parts
|
||||
function buildRigFromAttachments(currentPart, lastPart)
|
||||
local validSiblings = {}
|
||||
for _, sibling in pairs(currentPart.Parent:GetChildren()) do
|
||||
-- Don't find matching attachment in the current part being processed.
|
||||
-- Don't visit the last part visited again, this would cause an infinite loop.
|
||||
if sibling:IsA("BasePart") and sibling ~= currentPart and sibling ~= lastPart then
|
||||
table.insert(validSiblings, sibling)
|
||||
end
|
||||
end
|
||||
|
||||
local function processRigAttachment(attachment)
|
||||
for _, sibling in pairs(validSiblings) do
|
||||
local matchingAttachment = sibling:FindFirstChild(attachment.Name)
|
||||
if matchingAttachment then
|
||||
buildJoint(attachment, matchingAttachment)
|
||||
buildRigFromAttachments(matchingAttachment.Parent, currentPart)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, object in pairs(currentPart:GetChildren()) do
|
||||
if object:IsA("Attachment") and string.find(object.Name, "RigAttachment") then
|
||||
processRigAttachment(object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function getJointBetween(part0, part1)
|
||||
for _, obj in pairs(part1:GetChildren()) do
|
||||
if obj:IsA("Motor6D") and obj.Part0 == part0 then
|
||||
return obj
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function applyR15ToolPose(rig)
|
||||
local upperTorso = rig:FindFirstChild("UpperTorso")
|
||||
local rightUpperArm = rig:FindFirstChild("RightUpperArm")
|
||||
if upperTorso and rightUpperArm then
|
||||
local rightShoulderJoint = getJointBetween(upperTorso, rightUpperArm)
|
||||
if rightShoulderJoint then
|
||||
rightShoulderJoint.C1 = rightShoulderJoint.C1 * CFrame.new(0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 1, 0):inverse()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Applies the middle keyframe of a pose to a given character.
|
||||
local function applyPoseToCharacter(character, poseAnimationId)
|
||||
local poseKeyframSequence = game:GetService("KeyframeSequenceProvider"):GetKeyframeSequence(poseAnimationId)
|
||||
local keyframes = poseKeyframSequence:GetKeyframes()
|
||||
local poseKeyframe = keyframes[math.max(1, math.floor(#keyframes/2))]
|
||||
|
||||
local function recurApplyPoses(parentPose, poseObject)
|
||||
if parentPose then
|
||||
local joint = getJointBetween(character[parentPose.Name], character[poseObject.Name])
|
||||
if joint then
|
||||
joint.C1 = joint.C1 * poseObject.CFrame:inverse()
|
||||
end
|
||||
end
|
||||
|
||||
for _, subPose in pairs(poseObject:GetSubPoses()) do
|
||||
recurApplyPoses(poseObject, subPose)
|
||||
end
|
||||
end
|
||||
|
||||
for _, poseObj in pairs(poseKeyframe:GetPoses()) do
|
||||
recurApplyPoses(nil, poseObj)
|
||||
end
|
||||
end
|
||||
|
||||
if useR15 then
|
||||
-- Build R15 rig
|
||||
local humanoidRootPart = mannequin:WaitForChild("HumanoidRootPart")
|
||||
humanoidRootPart.CFrame = CFrame.new(Vector3.new(0, 5, 0)) * CFrame.Angles(0, math.pi, 0)
|
||||
humanoidRootPart.Anchored = true
|
||||
buildRigFromAttachments(humanoidRootPart)
|
||||
|
||||
if tool then
|
||||
applyR15ToolPose(mannequin)
|
||||
|
||||
local hand = mannequin:FindFirstChild("RightHand")
|
||||
local handle = tool:FindFirstChild("Handle")
|
||||
if hand and handle then
|
||||
local handGrip = hand:FindFirstChild("RightGripAttachment")
|
||||
if handGrip then
|
||||
handle.CFrame = hand.CFrame * handGrip.CFrame * tool.Grip:inverse()
|
||||
end
|
||||
end
|
||||
tool.Parent = mannequin
|
||||
elseif poseAnimationId then
|
||||
applyPoseToCharacter(mannequin, poseAnimationId)
|
||||
end
|
||||
end
|
||||
|
||||
function findFirstMatchingAttachment(model, name)
|
||||
for _, child in pairs(model:GetChildren()) do
|
||||
if child:IsA("Attachment") and child.Name == name then
|
||||
return child
|
||||
elseif not child:IsA("Accoutrement") and not child:IsA("Tool") then
|
||||
local foundAttachment = findFirstMatchingAttachment(child, name)
|
||||
if foundAttachment then
|
||||
return foundAttachment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, accoutrement in pairs(accoutrements) do
|
||||
local handle = accoutrement:FindFirstChild("Handle")
|
||||
if handle then
|
||||
local accoutrementAttachment = handle:FindFirstChildOfClass("Attachment")
|
||||
local characterAttachment = nil
|
||||
if accoutrementAttachment then
|
||||
characterAttachment = findFirstMatchingAttachment(mannequin, accoutrementAttachment.Name)
|
||||
end
|
||||
|
||||
local attachmentPart = nil
|
||||
if characterAttachment then
|
||||
attachmentPart = characterAttachment.Parent
|
||||
else
|
||||
attachmentPart = mannequin:FindFirstChild("Head")
|
||||
end
|
||||
|
||||
local attachmentCFrame = nil
|
||||
if characterAttachment then
|
||||
attachmentCFrame = characterAttachment.CFrame
|
||||
else
|
||||
attachmentCFrame = CFrame.new(0, 0.5, 0)
|
||||
end
|
||||
|
||||
local hatCFrame = nil
|
||||
if accoutrementAttachment then
|
||||
hatCFrame = accoutrementAttachment.CFrame
|
||||
else
|
||||
hatCFrame = accoutrement.AttachmentPoint
|
||||
end
|
||||
|
||||
handle.CFrame = attachmentPart.CFrame * attachmentCFrame * hatCFrame:inverse()
|
||||
handle.Anchored = true
|
||||
handle.Parent = mannequin
|
||||
end
|
||||
end
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Pants v1.0.1
|
||||
|
||||
assetUrl, fileExtension, x, y, baseUrl, mannequinId = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local mannequin = game:GetObjects(baseUrl.. "/asset/?id=" .. tostring(mannequinId))[1]
|
||||
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
||||
mannequin.Parent = workspace
|
||||
|
||||
local pants = game:GetObjects(assetUrl)[1]
|
||||
pants.Parent = mannequin
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Place v1.0.2
|
||||
|
||||
assetUrl, fileExtension, x, y, baseUrl, universeId = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
if universeId ~= nil then
|
||||
pcall(function() game:SetUniverseId(universeId) end)
|
||||
end
|
||||
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("StarterGui").ShowDevelopmentGui = false
|
||||
|
||||
game:Load(assetUrl)
|
||||
|
||||
-- Do this after again loading the place file to ensure that these values aren't changed when the place file is loaded.
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("StarterGui").ShowDevelopmentGui = false
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] false)
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Place Validation v1.0.0
|
||||
|
||||
assetUrl, baseUrl = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("StarterGui").ShowDevelopmentGui = false
|
||||
|
||||
local success, message = pcall(function()
|
||||
game:Load(assetUrl)
|
||||
end)
|
||||
if not success then
|
||||
return message
|
||||
end
|
||||
|
||||
-- Do this after again loading the place file to ensure that these values aren't changed when the place file is loaded.
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
game:GetService("StarterGui").ShowDevelopmentGui = false
|
||||
|
||||
return true
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Shirt v1.0.1
|
||||
|
||||
assetUrl, fileExtension, x, y, baseUrl, mannequinId = ...
|
||||
|
||||
pcall(function() game:GetService("ContentProvider"):SetBaseUrl(baseUrl) end)
|
||||
game:GetService("ScriptContext").ScriptsDisabled = true
|
||||
|
||||
local mannequin = game:GetObjects(baseUrl.. "/asset/?id=" .. tostring(mannequinId))[1]
|
||||
mannequin.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
|
||||
mannequin.Parent = workspace
|
||||
|
||||
local shirt = game:GetObjects(assetUrl)[1]
|
||||
shirt.Parent = mannequin
|
||||
|
||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true)
|
||||
Reference in New Issue
Block a user