add gs
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"lint": {
|
||||
"SameLineStatement": "fatal",
|
||||
"LocalShadow": "fatal",
|
||||
"FunctionUnused": "fatal",
|
||||
"ImportUnused": "fatal",
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
--[[
|
||||
Filename: CommonUtil.lua
|
||||
Written by: dbanks
|
||||
Description: Common work.
|
||||
--]]
|
||||
|
||||
|
||||
--[[ Classes ]]--
|
||||
local CommonUtil = {}
|
||||
|
||||
-- Concatenate these two tables, return result.
|
||||
function CommonUtil.TableConcat(t1,t2)
|
||||
for i=1,#t2 do
|
||||
t1[#t1+1] = t2[i]
|
||||
end
|
||||
return t1
|
||||
end
|
||||
|
||||
-- Instances have a "Name" field. Sort
|
||||
-- by that name,
|
||||
function CommonUtil.SortByName(items)
|
||||
local function compareInstanceNames(i1, i2)
|
||||
return (i1.Name < i2.Name)
|
||||
end
|
||||
table.sort(items, compareInstanceNames)
|
||||
return items
|
||||
end
|
||||
|
||||
-- Provides a nice syntax for creating Roblox instances.
|
||||
-- Example:
|
||||
-- local newPart = Utility.Create("Part") {
|
||||
-- Parent = workspace,
|
||||
-- Anchored = true,
|
||||
--
|
||||
-- --Create a SpecialMesh as a child of this part too
|
||||
-- Utility.Create("SpecialMesh") {
|
||||
-- MeshId = "rbxassetid://...",
|
||||
-- Scale = Vector3.new(0.5, 0.2, 10)
|
||||
-- }
|
||||
-- }
|
||||
function CommonUtil.Create(instanceType)
|
||||
return function(data)
|
||||
local obj = Instance.new(instanceType)
|
||||
local parent = nil
|
||||
for k, v in pairs(data) do
|
||||
if type(k) == 'number' then
|
||||
v.Parent = obj
|
||||
elseif k == 'Parent' then
|
||||
parent = v
|
||||
else
|
||||
obj[k] = v
|
||||
end
|
||||
end
|
||||
if parent then
|
||||
obj.Parent = parent
|
||||
end
|
||||
return obj
|
||||
end
|
||||
end
|
||||
|
||||
return CommonUtil
|
||||
@@ -0,0 +1,38 @@
|
||||
-- universal design constants for in-game ui style
|
||||
local Constants = {
|
||||
COLORS = {
|
||||
SLATE = Color3.fromRGB(35, 37, 39),
|
||||
FLINT = Color3.fromRGB(57, 59, 61),
|
||||
GRAPHITE = Color3.fromRGB(101, 102, 104),
|
||||
PUMICE = Color3.fromRGB(189, 190, 190),
|
||||
WHITE = Color3.fromRGB(255, 255, 255),
|
||||
},
|
||||
ERROR_PROMPT_HEIGHT = {
|
||||
Default = 236,
|
||||
XBox = 180,
|
||||
},
|
||||
ERROR_PROMPT_MIN_WIDTH = {
|
||||
Default = 320,
|
||||
XBox = 400,
|
||||
},
|
||||
ERROR_PROMPT_MAX_WIDTH = {
|
||||
Default = 400,
|
||||
XBox = 400,
|
||||
},
|
||||
ERROR_TITLE_FRAME_HEIGHT = {
|
||||
Default = 50,
|
||||
},
|
||||
SPLIT_LINE_THICKNESS = 1,
|
||||
BUTTON_CELL_PADDING = 10,
|
||||
BUTTON_HEIGHT = 36,
|
||||
SIDE_PADDING = 20,
|
||||
LAYOUT_PADDING = 20,
|
||||
SIDE_MARGIN = 20, -- When resizing according to screen size, reserve with side margins
|
||||
|
||||
PRIMARY_BUTTON_TEXTURE = "rbxasset://textures/ui/ErrorPrompt/PrimaryButton.png",
|
||||
SECONDARY_BUTTON_TEXTURE = "rbxasset://textures/ui/ErrorPrompt/SecondaryButton.png",
|
||||
SHIMMER_TEXTURE = "rbxasset://textures/ui/LuaApp/graphic/shimmer_darkTheme.png",
|
||||
OVERLAY_TEXTURE = "rbxasset://textures/ui/ErrorPrompt/ShimmerOverlay.png",
|
||||
}
|
||||
|
||||
return Constants
|
||||
@@ -0,0 +1,67 @@
|
||||
if not settings():GetFFlag("CoreScriptFasterCreate") then
|
||||
return function(className, defaultParent)
|
||||
return function(propertyList)
|
||||
local object = Instance.new(className)
|
||||
local parent = nil
|
||||
|
||||
for index, value in next, propertyList do
|
||||
if typeof(index) == 'string' then
|
||||
if index == 'Parent' then
|
||||
parent = value
|
||||
else
|
||||
object[index] = value
|
||||
end
|
||||
else
|
||||
local valueType = typeof(value)
|
||||
if valueType == 'function' then
|
||||
value(object)
|
||||
elseif valueType == 'Instance' then
|
||||
value.Parent = object
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if parent then
|
||||
object.Parent = parent
|
||||
end
|
||||
|
||||
if object.Parent == nil then
|
||||
object.Parent = defaultParent
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return function(className, defaultParent)
|
||||
return function(propertyList)
|
||||
local object = Instance.new(className)
|
||||
local parent = nil
|
||||
|
||||
for index, value in next, propertyList do
|
||||
if type(index) == 'string' then
|
||||
if index == 'Parent' then
|
||||
parent = value
|
||||
else
|
||||
object[index] = value
|
||||
end
|
||||
else
|
||||
local valueType = typeof(value)
|
||||
if valueType == 'function' then
|
||||
value(object)
|
||||
elseif valueType == 'Instance' then
|
||||
value.Parent = object
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if parent then
|
||||
object.Parent = parent
|
||||
elseif defaultParent then
|
||||
object.Parent = defaultParent
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
--[[
|
||||
A component that establishes a connection to a Roblox event when it is rendered.
|
||||
]]
|
||||
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
|
||||
local EventConnection = Roact.Component:extend("EventConnection")
|
||||
|
||||
function EventConnection:init()
|
||||
self.connection = nil
|
||||
end
|
||||
|
||||
--[[
|
||||
Render the child component so that EventConnections can be nested like so:
|
||||
|
||||
Roact.createElement(EventConnection, {
|
||||
event = UserInputService.InputBegan,
|
||||
callback = inputBeganCallback,
|
||||
}, {
|
||||
Roact.createElement(EventConnection, {
|
||||
event = UserInputService.InputChanged,
|
||||
callback = inputChangedCallback,
|
||||
})
|
||||
})
|
||||
]]
|
||||
function EventConnection:render()
|
||||
return Roact.oneChild(self.props[Roact.Children])
|
||||
end
|
||||
|
||||
function EventConnection:didMount()
|
||||
local event = self.props.event
|
||||
local callback = self.props.callback
|
||||
|
||||
self.connection = event:Connect(callback)
|
||||
end
|
||||
|
||||
function EventConnection:didUpdate(oldProps)
|
||||
if self.props.event ~= oldProps.event or self.props.callback ~= oldProps.callback then
|
||||
self.connection:Disconnect()
|
||||
|
||||
self.connection = self.props.event:Connect(self.props.callback)
|
||||
end
|
||||
end
|
||||
|
||||
function EventConnection:willUnmount()
|
||||
self.connection:Disconnect()
|
||||
|
||||
self.connection = nil
|
||||
end
|
||||
|
||||
return EventConnection
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
game:DefineFastFlag("FixDialogServerWait", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("FixDialogServerWait")
|
||||
end
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
-- This Flag is a descendant of the Common folder instead of Modules/Flags
|
||||
-- because it needs to be accessible by both Modules/InGameChat and
|
||||
-- Modules/Server/ClientChat/ChatWindowInstaller.
|
||||
--
|
||||
-- Since the server only has access to the Common and Server folders, it's
|
||||
-- placed here so both parts of the codebase can access it.
|
||||
|
||||
game:DefineFastFlag("RoactBubbleChat", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("RoactBubbleChat")
|
||||
end
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
-- This Flag is a descendant of the Common folder instead of Modules/Flags
|
||||
-- because it needs to be accessible by both Modules/Common/LegacyThumbnailUrls and
|
||||
-- other places like Emotes, InspectAndBuy
|
||||
--
|
||||
-- Since the server only has access to the Common and Server folders, it's
|
||||
-- placed here so both parts of the codebase can access it.
|
||||
|
||||
game:DefineFastFlag("UseThumbnailUrl", false)
|
||||
|
||||
return function()
|
||||
return game:GetFastFlag("UseThumbnailUrl")
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
local GAME_I18N_URL = string.format("https://gameinternationalization.%s", Url.DOMAIN)
|
||||
|
||||
local GameRequests = {}
|
||||
|
||||
--[[
|
||||
This endpoint (gameinternationalization/v1/name-description/games/{gameId})
|
||||
returns all of the available localized names + descriptions for a game.
|
||||
Docs: https://gameinternationalization.roblox.com/docs#!/NameDescription/get_v1_name_description_games_gameId
|
||||
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"name": "string",
|
||||
"description": "string",
|
||||
"languageCode": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]]
|
||||
function GameRequests.GetNamesAndDescriptions(requestImpl, gameId)
|
||||
local url = string.format("%sv1/name-description/games/%s", GAME_I18N_URL, gameId)
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
|
||||
--[[
|
||||
This endpoint (gameinternationalization/v1/source-language/games/{gameId})
|
||||
returns the games default language
|
||||
Docs: https://gameinternationalization.roblox.com/docs#!/SourceLanguage/get_v1_source_language_games_gameId
|
||||
|
||||
{
|
||||
"name": "English",
|
||||
"nativeName": "English",
|
||||
"languageCode": "en"
|
||||
}
|
||||
]]
|
||||
function GameRequests.GetSourceLanguage(requestImpl, gameId)
|
||||
local url = string.format("%sv1/source-language/games/%s", GAME_I18N_URL, gameId)
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
|
||||
--[[
|
||||
This endpoint (locale.roblox.com/v1/locales)
|
||||
returns information about the supported languages on Roblox
|
||||
Docs: https://locale.roblox.com/docs#!/Locale/get_v1_locales_user_locale
|
||||
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"locale": {
|
||||
"id": 0,
|
||||
"locale": "string",
|
||||
"name": "string",
|
||||
"nativeName": "string",
|
||||
"language": {
|
||||
"id": 0,
|
||||
"name": "string",
|
||||
"nativeName": "string",
|
||||
"languageCode": "string"
|
||||
}
|
||||
},
|
||||
|
||||
"isEnabledForFullExperience": true,
|
||||
"isEnabledForSignupAndLogin": true,
|
||||
"isEnabledForInGameUgc": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]]
|
||||
function GameRequests.GetSupportedLanguages(requestImpl)
|
||||
local url = string.format("%sv1/locales", Url.LOCALE)
|
||||
return requestImpl(url, "GET")
|
||||
end
|
||||
|
||||
return GameRequests
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
--!nocheck
|
||||
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
|
||||
return function()
|
||||
return Enum.Platform.XBoxOne == UserInputService:GetPlatform() and Enum.QualityLevel.Level21 or Enum.QualityLevel.Automatic
|
||||
end
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Promise = require(CorePackages.Promise)
|
||||
|
||||
local GameRequests = require(script.Parent.GameRequests)
|
||||
|
||||
local SupportedLanguagesFetched = false
|
||||
local StartedFetchingSupportedLanaguages = false
|
||||
local FetchedSupportedLanguagesEvent = Instance.new("BindableEvent")
|
||||
local CachedSupportedLanguages = nil
|
||||
|
||||
local FFlagFixGetGameNameAndDescription = game:DefineFastFlag("FixGetGameNameAndDescription", false)
|
||||
|
||||
local FALLBACK_LANGUAGE_CONSTANT = "FALLBACK"
|
||||
|
||||
local FALLBACK_SOURCE_LOCALE = "en-us"
|
||||
|
||||
local function GetSupportedLanguagesPromise(networkImpl)
|
||||
if SupportedLanguagesFetched then
|
||||
return Promise.resolve(CachedSupportedLanguages)
|
||||
elseif StartedFetchingSupportedLanaguages then
|
||||
return Promise.new(function(resolve, reject)
|
||||
local success = FetchedSupportedLanguagesEvent.Event:Wait()
|
||||
if success then
|
||||
resolve(CachedSupportedLanguages)
|
||||
else
|
||||
reject()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
StartedFetchingSupportedLanaguages = true
|
||||
|
||||
return GameRequests.GetSupportedLanguages(networkImpl):andThen(function(result)
|
||||
if FFlagFixGetGameNameAndDescription then
|
||||
SupportedLanguagesFetched = true
|
||||
end
|
||||
CachedSupportedLanguages = result
|
||||
FetchedSupportedLanguagesEvent:Fire(true)
|
||||
return Promise.resolve(result)
|
||||
end,
|
||||
function()
|
||||
StartedFetchingSupportedLanaguages = false
|
||||
FetchedSupportedLanguagesEvent:Fire(false)
|
||||
return Promise.reject()
|
||||
end)
|
||||
end
|
||||
|
||||
local function PerGameCachedRequestFactoryFunction(Request)
|
||||
local cache = {}
|
||||
|
||||
return function(networkImpl, gameId)
|
||||
if cache[gameId] then
|
||||
if cache[gameId].Fetched then
|
||||
return Promise.resolve(cache[gameId].Result)
|
||||
else
|
||||
local success = cache[gameId].FinishedEvent.Event:Wait()
|
||||
if success then
|
||||
return Promise.resolve(cache[gameId].Result)
|
||||
else
|
||||
return Promise.reject()
|
||||
end
|
||||
end
|
||||
else
|
||||
cache[gameId] = {
|
||||
Fetched = false,
|
||||
FinishedEvent = Instance.new("BindableEvent"),
|
||||
Result = nil,
|
||||
}
|
||||
|
||||
return Request(networkImpl, gameId):andThen(function(result)
|
||||
cache[gameId].Fetched = true
|
||||
cache[gameId].Result = result
|
||||
cache[gameId].FinishedEvent:Fire(true)
|
||||
return Promise.resolve(result)
|
||||
end,
|
||||
function()
|
||||
cache[gameId].FinishedEvent:Fire(false)
|
||||
return Promise.reject()
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function ProcessNamesAndDescriptionsResult(result)
|
||||
local data = result.responseBody.data
|
||||
local gameNames = {}
|
||||
local gameDescriptions = {}
|
||||
|
||||
for _, nameDescription in ipairs(data) do
|
||||
local languageCode = nameDescription.languageCode or FALLBACK_LANGUAGE_CONSTANT
|
||||
if gameNames[languageCode] == nil then
|
||||
gameNames[languageCode] = nameDescription.name
|
||||
end
|
||||
if gameDescriptions[languageCode] == nil then
|
||||
gameDescriptions[languageCode] = nameDescription.description
|
||||
end
|
||||
end
|
||||
|
||||
return gameNames, gameDescriptions
|
||||
end
|
||||
|
||||
local function ProcessSupportedLanaguagesResult(result)
|
||||
local data = result.responseBody.data
|
||||
local languageCodeMap = {}
|
||||
|
||||
for _, localeInfo in ipairs(data) do
|
||||
-- Locale being repeated here twice looks wrong but it is correct.
|
||||
-- Web passes in the locales with _ as a seperator but we use - on the client
|
||||
languageCodeMap[localeInfo.locale.language.languageCode] = localeInfo.locale.locale:gsub("_", "-")
|
||||
end
|
||||
|
||||
return languageCodeMap
|
||||
end
|
||||
|
||||
local function ProcessSourceLanguageResult(result)
|
||||
local data = result.responseBody
|
||||
|
||||
return data.languageCode
|
||||
end
|
||||
|
||||
local GetNamesAndDescriptionsPromise = PerGameCachedRequestFactoryFunction(GameRequests.GetNamesAndDescriptions)
|
||||
local GetSourceLanguagePromise = PerGameCachedRequestFactoryFunction(GameRequests.GetSourceLanguage)
|
||||
|
||||
return function(networkImpl, gameId)
|
||||
local namesAndDescriptionsPromise = GetNamesAndDescriptionsPromise(networkImpl, gameId)
|
||||
local supportedLanguagesPromise = GetSupportedLanguagesPromise(networkImpl)
|
||||
local sourceLanguagesPromise = GetSourceLanguagePromise(networkImpl, gameId)
|
||||
|
||||
return Promise.all(
|
||||
namesAndDescriptionsPromise,
|
||||
supportedLanguagesPromise,
|
||||
sourceLanguagesPromise):andThen(function(results)
|
||||
local namesAndDescriptionsResult = results[1]
|
||||
local supportedLanaguesResult = results[2]
|
||||
local sourceLanagueResult = results[3]
|
||||
|
||||
local gameNamesLanguageCodeMap, gameDescriptionsLanguageCodeMap = ProcessNamesAndDescriptionsResult(
|
||||
namesAndDescriptionsResult)
|
||||
local languageCodeMap = ProcessSupportedLanaguagesResult(supportedLanaguesResult)
|
||||
local sourceLangaugeCode = ProcessSourceLanguageResult(sourceLanagueResult)
|
||||
local sourceLocale = languageCodeMap[sourceLangaugeCode] or FALLBACK_SOURCE_LOCALE
|
||||
|
||||
local gameNameLocaleMap = {}
|
||||
for languageCode, name in pairs(gameNamesLanguageCodeMap) do
|
||||
local locale = languageCodeMap[languageCode]
|
||||
if locale then
|
||||
gameNameLocaleMap[locale] = name
|
||||
end
|
||||
end
|
||||
|
||||
if gameNameLocaleMap[sourceLocale] == nil then
|
||||
gameNameLocaleMap[sourceLocale] = gameNamesLanguageCodeMap[FALLBACK_LANGUAGE_CONSTANT]
|
||||
end
|
||||
|
||||
local gameDescriptionsLocaleMap = {}
|
||||
for languageCode, description in pairs(gameDescriptionsLanguageCodeMap) do
|
||||
local locale = languageCodeMap[languageCode]
|
||||
if locale then
|
||||
gameDescriptionsLocaleMap[locale] = description
|
||||
end
|
||||
end
|
||||
|
||||
if gameDescriptionsLocaleMap[sourceLocale] == nil then
|
||||
gameDescriptionsLocaleMap[sourceLocale] = gameDescriptionsLanguageCodeMap[FALLBACK_LANGUAGE_CONSTANT]
|
||||
end
|
||||
|
||||
return gameNameLocaleMap, gameDescriptionsLocaleMap, sourceLocale
|
||||
end,
|
||||
function()
|
||||
return Promise.reject()
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,105 @@
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
-- wait for the first of the passed signals to fire
|
||||
local function waitForFirst(...)
|
||||
local shunt = Instance.new("BindableEvent")
|
||||
local slots = {...}
|
||||
|
||||
local function fire(...)
|
||||
for i = 1, #slots do
|
||||
slots[i]:Disconnect()
|
||||
end
|
||||
|
||||
return shunt:Fire(...)
|
||||
end
|
||||
|
||||
for i = 1, #slots do
|
||||
slots[i] = slots[i]:Connect(fire)
|
||||
end
|
||||
|
||||
return shunt.Event:Wait()
|
||||
end
|
||||
|
||||
local HumanoidReadyUtil = {}
|
||||
|
||||
-- registers a humanoidReady(player: Player, character: Model, humanoid: Humanoid) callback and
|
||||
-- invokes it immediately for existing player character humanoids.
|
||||
--
|
||||
-- Unregistering is not currently supported.
|
||||
--
|
||||
-- This can't just be an event because we need to invoke it immediately for existing eligable player
|
||||
-- character humanoids, but would not for any existing callbacks.
|
||||
--
|
||||
-- For now this is about sharing code, not sharing event connections. Supporting this would really
|
||||
-- complicate the code and we don't currently have multiple systems that could benefit from this
|
||||
-- sharing that would all be active on a single client.
|
||||
function HumanoidReadyUtil.registerHumanoidReady(humanoidReady)
|
||||
|
||||
local function characterAdded(player, character)
|
||||
-- Avoiding memory leaks in the face of Character/Humanoid/RootPart lifetime has a few complications:
|
||||
-- * character deparenting is a Remove instead of a Destroy, so signals are not cleaned up automatically.
|
||||
-- ** must use a waitForFirst on everything and listen for hierarchy changes.
|
||||
-- * the character might not be in the dm by the time CharacterAdded fires
|
||||
-- ** constantly check consistency with player.Character and abort if CharacterAdded is fired again
|
||||
-- * Humanoid may not exist immediately, and by the time it's inserted the character might be deparented.
|
||||
-- * RootPart probably won't exist immediately.
|
||||
-- ** by the time RootPart is inserted and Humanoid.RootPart is set, the character or the humanoid might be deparented.
|
||||
|
||||
if not character.Parent then
|
||||
waitForFirst(character.AncestryChanged, player.CharacterAdded)
|
||||
end
|
||||
|
||||
if player.Character ~= character or not character.Parent then
|
||||
return
|
||||
end
|
||||
|
||||
local humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
while character:IsDescendantOf(game) and not humanoid do
|
||||
waitForFirst(character.ChildAdded, character.AncestryChanged, player.CharacterAdded)
|
||||
humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
end
|
||||
|
||||
if player.Character ~= character or not character:IsDescendantOf(game) then
|
||||
return
|
||||
end
|
||||
|
||||
-- must rely on HumanoidRootPart naming because Humanoid.RootPart does not fire changed signals
|
||||
local rootPart = character:FindFirstChild("HumanoidRootPart")
|
||||
while character:IsDescendantOf(game) and not rootPart do
|
||||
waitForFirst(character.ChildAdded, character.AncestryChanged, humanoid.AncestryChanged, player.CharacterAdded)
|
||||
rootPart = character:FindFirstChild("HumanoidRootPart")
|
||||
end
|
||||
|
||||
if rootPart and humanoid:IsDescendantOf(game) and character:IsDescendantOf(game) and player.Character == character then
|
||||
humanoidReady(player, character, humanoid)
|
||||
end
|
||||
end
|
||||
|
||||
local function playerAdded(player)
|
||||
local characterAddedConn = player.CharacterAdded:Connect(function(character)
|
||||
characterAdded(player, character)
|
||||
end)
|
||||
|
||||
-- Players are Removed, not Destroyed, by replication so we must clean up
|
||||
local ancestryChangedConn
|
||||
ancestryChangedConn = player.AncestryChanged:Connect(function(_child, parent)
|
||||
if not game:IsAncestorOf(parent) then
|
||||
ancestryChangedConn:Disconnect()
|
||||
characterAddedConn:Disconnect()
|
||||
end
|
||||
end)
|
||||
|
||||
local character = player.Character
|
||||
if character then
|
||||
characterAdded(player, character)
|
||||
end
|
||||
end
|
||||
|
||||
-- Track all players (including local player on the client)
|
||||
Players.PlayerAdded:Connect(playerAdded)
|
||||
for _, player in pairs(Players:GetPlayers()) do
|
||||
playerAdded(player)
|
||||
end
|
||||
end
|
||||
|
||||
return HumanoidReadyUtil
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
local RobloxGui = game:GetService("CoreGui"):WaitForChild("RobloxGui")
|
||||
|
||||
local GetFFlagUseThumbnailUrl = require(RobloxGui.Modules.Common.Flags.GetFFlagUseThumbnailUrl)
|
||||
|
||||
if GetFFlagUseThumbnailUrl() then
|
||||
local BaseUrl = ContentProvider.BaseUrl:lower()
|
||||
BaseUrl = string.gsub(BaseUrl, "/m.", "/www.")
|
||||
BaseUrl = string.gsub(BaseUrl, "/www.", "/thumbnails.")
|
||||
BaseUrl = string.gsub(BaseUrl, "http:", "https:")
|
||||
|
||||
return {
|
||||
Headshot = "rbxthumb://type=AvatarHeadShot&id=%d&w=%d&h=%d",
|
||||
Bust = BaseUrl .. "v1/users/avatar-bust?userIds=%d&size=%dx%d&format=Png&isCircular=false",
|
||||
Thumbnail = "rbxthumb://type=Avatar&id=%d&w=%d&h=%d",
|
||||
}
|
||||
else
|
||||
--Prefer Rbxthumb urls for new work!
|
||||
local BaseUrl = ContentProvider.BaseUrl:lower()
|
||||
BaseUrl = string.gsub(BaseUrl, "/m.", "/www.")
|
||||
BaseUrl = string.gsub(BaseUrl, "http:", "https:")
|
||||
|
||||
return {
|
||||
Headshot = BaseUrl .. "headshot-thumbnail/image?width=%d&height=%d&format=png&userId=%d",
|
||||
Bust = BaseUrl .. "bust-thumbnail/image?width=%d&height=%d&format=png&userId=%d",
|
||||
Thumbnail = BaseUrl .. "avatar-thumbnail/image?width=%d&height=%d&format=png&userId=%d",
|
||||
}
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
-- // FileName: ObjectPool.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: An object pool class used to avoid unnecessarily instantiating Instances.
|
||||
|
||||
local module = {}
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function methods:GetInstance(className)
|
||||
if self.InstancePoolsByClass[className] == nil then
|
||||
self.InstancePoolsByClass[className] = {}
|
||||
end
|
||||
local availableInstances = #self.InstancePoolsByClass[className]
|
||||
if availableInstances > 0 then
|
||||
local instance = self.InstancePoolsByClass[className][availableInstances]
|
||||
table.remove(self.InstancePoolsByClass[className])
|
||||
return instance
|
||||
end
|
||||
return Instance.new(className)
|
||||
end
|
||||
|
||||
function methods:ReturnInstance(instance)
|
||||
if self.InstancePoolsByClass[instance.ClassName] == nil then
|
||||
self.InstancePoolsByClass[instance.ClassName] = {}
|
||||
end
|
||||
if #self.InstancePoolsByClass[instance.ClassName] < self.PoolSizePerType then
|
||||
table.insert(self.InstancePoolsByClass[instance.ClassName], instance)
|
||||
else
|
||||
instance:Destroy()
|
||||
end
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new(poolSizePerType)
|
||||
local obj = setmetatable({}, methods)
|
||||
obj.InstancePoolsByClass = {}
|
||||
obj.Name = "ObjectPool"
|
||||
obj.PoolSizePerType = poolSizePerType
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
@@ -0,0 +1,54 @@
|
||||
--[[
|
||||
Filename: PolicyService.lua
|
||||
Written by: ben
|
||||
Description: Handles all policy service calls in lua for core scripts
|
||||
--]]
|
||||
|
||||
local PlayersService = game:GetService('Players')
|
||||
|
||||
local isSubjectToChinaPolicies = true
|
||||
local policyTable
|
||||
local initialized = false
|
||||
local initAsyncCalledOnce = false
|
||||
|
||||
local initializedEvent = Instance.new("BindableEvent")
|
||||
|
||||
--[[ Classes ]]--
|
||||
local PolicyService = {}
|
||||
|
||||
function PolicyService:InitAsync()
|
||||
if _G.__TESTEZ_RUNNING_TEST__ then
|
||||
-- Return here in the case of unit tests
|
||||
return
|
||||
end
|
||||
|
||||
if initialized then return end
|
||||
if initAsyncCalledOnce then
|
||||
initializedEvent.Event:Wait()
|
||||
return
|
||||
end
|
||||
initAsyncCalledOnce = true
|
||||
|
||||
local localPlayer = PlayersService.LocalPlayer
|
||||
while not localPlayer do
|
||||
PlayersService.PlayerAdded:wait()
|
||||
localPlayer = PlayersService.LocalPlayer
|
||||
end
|
||||
|
||||
pcall(function() policyTable = game:GetService("PolicyService"):GetPolicyInfoForPlayerAsync(localPlayer) end)
|
||||
if policyTable then
|
||||
isSubjectToChinaPolicies = policyTable["IsSubjectToChinaPolicies"]
|
||||
end
|
||||
|
||||
initialized = true
|
||||
initializedEvent:Fire()
|
||||
end
|
||||
|
||||
function PolicyService:IsSubjectToChinaPolicies()
|
||||
self:InitAsync()
|
||||
|
||||
return isSubjectToChinaPolicies
|
||||
end
|
||||
|
||||
|
||||
return PolicyService
|
||||
@@ -0,0 +1,480 @@
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local Rigging = {}
|
||||
|
||||
-- Gravity that joint friction values were tuned under.
|
||||
local REFERENCE_GRAVITY = 196.2
|
||||
|
||||
-- ReferenceMass values from mass of child part. Used to normalized "stiffness" for differently
|
||||
-- sized avatars (with different mass).
|
||||
local DEFAULT_MAX_FRICTION_TORQUE = 500
|
||||
|
||||
local HEAD_LIMITS = {
|
||||
UpperAngle = 45,
|
||||
TwistLowerAngle = -40,
|
||||
TwistUpperAngle = 40,
|
||||
FrictionTorque = 400,
|
||||
ReferenceMass = 1.0249234437943,
|
||||
}
|
||||
|
||||
local WAIST_LIMITS = {
|
||||
UpperAngle = 20,
|
||||
TwistLowerAngle = -40,
|
||||
TwistUpperAngle = 20,
|
||||
FrictionTorque = 750,
|
||||
ReferenceMass = 2.861558675766,
|
||||
}
|
||||
|
||||
local ANKLE_LIMITS = {
|
||||
UpperAngle = 10,
|
||||
TwistLowerAngle = -10,
|
||||
TwistUpperAngle = 10,
|
||||
ReferenceMass = 0.43671694397926,
|
||||
}
|
||||
|
||||
local ELBOW_LIMITS = {
|
||||
-- Elbow is basically a hinge, but allow some twist for Supination and Pronation
|
||||
UpperAngle = 20,
|
||||
TwistLowerAngle = 5,
|
||||
TwistUpperAngle = 120,
|
||||
ReferenceMass = 0.70196455717087,
|
||||
}
|
||||
|
||||
local WRIST_LIMITS = {
|
||||
UpperAngle = 30,
|
||||
TwistLowerAngle = -10,
|
||||
TwistUpperAngle = 10,
|
||||
ReferenceMass = 0.69132566452026,
|
||||
}
|
||||
|
||||
local KNEE_LIMITS = {
|
||||
UpperAngle = 5,
|
||||
TwistLowerAngle = -120,
|
||||
TwistUpperAngle = -5,
|
||||
ReferenceMass = 0.65389388799667,
|
||||
}
|
||||
|
||||
local SHOULDER_LIMITS = {
|
||||
UpperAngle = 110,
|
||||
TwistLowerAngle = -85,
|
||||
TwistUpperAngle = 85,
|
||||
FrictionTorque = 600,
|
||||
ReferenceMass = 0.90918225049973,
|
||||
}
|
||||
|
||||
local HIP_LIMITS = {
|
||||
UpperAngle = 40,
|
||||
TwistLowerAngle = -5,
|
||||
TwistUpperAngle = 80,
|
||||
FrictionTorque = 600,
|
||||
ReferenceMass = 1.9175016880035,
|
||||
}
|
||||
|
||||
local R6_HEAD_LIMITS = {
|
||||
UpperAngle = 30,
|
||||
TwistLowerAngle = -40,
|
||||
TwistUpperAngle = 40,
|
||||
}
|
||||
|
||||
local R6_SHOULDER_LIMITS = {
|
||||
UpperAngle = 110,
|
||||
TwistLowerAngle = -85,
|
||||
TwistUpperAngle = 85,
|
||||
}
|
||||
|
||||
local R6_HIP_LIMITS = {
|
||||
UpperAngle = 40,
|
||||
TwistLowerAngle = -5,
|
||||
TwistUpperAngle = 80,
|
||||
}
|
||||
|
||||
local V3_ZERO = Vector3.new()
|
||||
local V3_UP = Vector3.new(0, 1, 0)
|
||||
local V3_DOWN = Vector3.new(0, -1, 0)
|
||||
local V3_RIGHT = Vector3.new(1, 0, 0)
|
||||
local V3_LEFT = Vector3.new(-1, 0, 0)
|
||||
|
||||
-- To model shoulder cone and twist limits correctly we really need the primary axis of the UpperArm
|
||||
-- to be going down the limb. the waist and neck joints attachments actually have the same problem
|
||||
-- of non-ideal axis orientation, but it's not as noticable there since the limits for natural
|
||||
-- motion are tighter for those joints anyway.
|
||||
local R15_ADDITIONAL_ATTACHMENTS = {
|
||||
{"UpperTorso", "RightShoulderRagdollAttachment", CFrame.fromMatrix(V3_ZERO, V3_RIGHT, V3_UP), "RightShoulderRigAttachment"},
|
||||
{"RightUpperArm", "RightShoulderRagdollAttachment", CFrame.fromMatrix(V3_ZERO, V3_DOWN, V3_RIGHT), "RightShoulderRigAttachment"},
|
||||
{"UpperTorso", "LeftShoulderRagdollAttachment", CFrame.fromMatrix(V3_ZERO, V3_LEFT, V3_UP), "LeftShoulderRigAttachment"},
|
||||
{"LeftUpperArm", "LeftShoulderRagdollAttachment", CFrame.fromMatrix(V3_ZERO, V3_DOWN, V3_LEFT), "LeftShoulderRigAttachment"},
|
||||
}
|
||||
-- { { Part0 name (parent), Part1 name (child, parent of joint), attachmentName, limits }, ... }
|
||||
local R15_RAGDOLL_RIG = {
|
||||
{"UpperTorso", "Head", "NeckRigAttachment", HEAD_LIMITS},
|
||||
|
||||
{"LowerTorso", "UpperTorso", "WaistRigAttachment", WAIST_LIMITS},
|
||||
|
||||
{"UpperTorso", "LeftUpperArm", "LeftShoulderRagdollAttachment", SHOULDER_LIMITS},
|
||||
{"LeftUpperArm", "LeftLowerArm", "LeftElbowRigAttachment", ELBOW_LIMITS},
|
||||
{"LeftLowerArm", "LeftHand", "LeftWristRigAttachment", WRIST_LIMITS},
|
||||
|
||||
{"UpperTorso", "RightUpperArm", "RightShoulderRagdollAttachment", SHOULDER_LIMITS},
|
||||
{"RightUpperArm", "RightLowerArm", "RightElbowRigAttachment", ELBOW_LIMITS},
|
||||
{"RightLowerArm", "RightHand", "RightWristRigAttachment", WRIST_LIMITS},
|
||||
|
||||
{"LowerTorso", "LeftUpperLeg", "LeftHipRigAttachment", HIP_LIMITS},
|
||||
{"LeftUpperLeg", "LeftLowerLeg", "LeftKneeRigAttachment", KNEE_LIMITS},
|
||||
{"LeftLowerLeg", "LeftFoot", "LeftAnkleRigAttachment", ANKLE_LIMITS},
|
||||
|
||||
{"LowerTorso", "RightUpperLeg", "RightHipRigAttachment", HIP_LIMITS},
|
||||
{"RightUpperLeg", "RightLowerLeg", "RightKneeRigAttachment", KNEE_LIMITS},
|
||||
{"RightLowerLeg", "RightFoot", "RightAnkleRigAttachment", ANKLE_LIMITS},
|
||||
}
|
||||
-- { { Part0 name, Part1 name }, ... }
|
||||
local R15_NO_COLLIDES = {
|
||||
{"LowerTorso", "LeftUpperArm"},
|
||||
{"LeftUpperArm", "LeftHand"},
|
||||
|
||||
{"LowerTorso", "RightUpperArm"},
|
||||
{"RightUpperArm", "RightHand"},
|
||||
|
||||
{"LeftUpperLeg", "RightUpperLeg"},
|
||||
|
||||
{"UpperTorso", "RightUpperLeg"},
|
||||
{"RightUpperLeg", "RightFoot"},
|
||||
|
||||
{"UpperTorso", "LeftUpperLeg"},
|
||||
{"LeftUpperLeg", "LeftFoot"},
|
||||
|
||||
-- Support weird R15 rigs
|
||||
{"UpperTorso", "LeftLowerLeg"},
|
||||
{"UpperTorso", "RightLowerLeg"},
|
||||
{"LowerTorso", "LeftLowerLeg"},
|
||||
{"LowerTorso", "RightLowerLeg"},
|
||||
|
||||
{"UpperTorso", "LeftLowerArm"},
|
||||
{"UpperTorso", "RightLowerArm"},
|
||||
|
||||
{"Head", "LeftUpperArm"},
|
||||
{"Head", "RightUpperArm"},
|
||||
}
|
||||
-- { { Motor6D name, Part name }, ...}, must be in tree order, important for ApplyJointVelocities
|
||||
local R15_MOTOR6DS = {
|
||||
{"Waist", "UpperTorso"},
|
||||
|
||||
{"Neck", "Head"},
|
||||
|
||||
{"LeftShoulder", "LeftUpperArm"},
|
||||
{"LeftElbow", "LeftLowerArm"},
|
||||
{"LeftWrist", "LeftHand"},
|
||||
|
||||
{"RightShoulder", "RightUpperArm"},
|
||||
{"RightElbow", "RightLowerArm"},
|
||||
{"RightWrist", "RightHand"},
|
||||
|
||||
{"LeftHip", "LeftUpperLeg"},
|
||||
{"LeftKnee", "LeftLowerLeg"},
|
||||
{"LeftAnkle", "LeftFoot"},
|
||||
|
||||
{"RightHip", "RightUpperLeg"},
|
||||
{"RightKnee", "RightLowerLeg"},
|
||||
{"RightAnkle", "RightFoot"},
|
||||
}
|
||||
|
||||
-- R6 has hard coded part sizes and does not have a full set of rig Attachments.
|
||||
local R6_ADDITIONAL_ATTACHMENTS = {
|
||||
{"Head", "NeckAttachment", CFrame.new(0, -0.5, 0)},
|
||||
|
||||
{"Torso", "RightShoulderRagdollAttachment", CFrame.fromMatrix(Vector3.new(1, 0.5, 0), V3_RIGHT, V3_UP)},
|
||||
{"Right Arm", "RightShoulderRagdollAttachment", CFrame.fromMatrix(Vector3.new(-0.5, 0.5, 0), V3_DOWN, V3_RIGHT)},
|
||||
|
||||
{"Torso", "LeftShoulderRagdollAttachment", CFrame.fromMatrix(Vector3.new(-1, 0.5, 0), V3_LEFT, V3_UP)},
|
||||
{"Left Arm", "LeftShoulderRagdollAttachment", CFrame.fromMatrix(Vector3.new(0.5, 0.5, 0), V3_DOWN, V3_LEFT)},
|
||||
|
||||
{"Torso", "RightHipAttachment", CFrame.new(0.5, -1, 0)},
|
||||
{"Right Leg", "RightHipAttachment", CFrame.new(0, 1, 0)},
|
||||
|
||||
{"Torso", "LeftHipAttachment", CFrame.new(-0.5, -1, 0)},
|
||||
{"Left Leg", "LeftHipAttachment", CFrame.new(0, 1, 0)},
|
||||
}
|
||||
-- R6 rig tables use the same table structures as R15.
|
||||
local R6_RAGDOLL_RIG = {
|
||||
{"Torso", "Head", "NeckAttachment", R6_HEAD_LIMITS},
|
||||
|
||||
{"Torso", "Left Leg", "LeftHipAttachment", R6_HIP_LIMITS},
|
||||
{"Torso", "Right Leg", "RightHipAttachment", R6_HIP_LIMITS},
|
||||
|
||||
{"Torso", "Left Arm", "LeftShoulderRagdollAttachment", R6_SHOULDER_LIMITS},
|
||||
{"Torso", "Right Arm", "RightShoulderRagdollAttachment", R6_SHOULDER_LIMITS},
|
||||
}
|
||||
local R6_NO_COLLIDES = {
|
||||
{"Left Leg", "Right Leg"},
|
||||
{"Head", "Right Arm"},
|
||||
{"Head", "Left Arm"},
|
||||
}
|
||||
local R6_MOTOR6DS = {
|
||||
{"Neck", "Torso"},
|
||||
{"Left Shoulder", "Torso"},
|
||||
{"Right Shoulder", "Torso"},
|
||||
{"Left Hip", "Torso"},
|
||||
{"Right Hip", "Torso"},
|
||||
}
|
||||
|
||||
local BALL_SOCKET_NAME = "RagdollBallSocket"
|
||||
local NO_COLLIDE_NAME = "RagdollNoCollision"
|
||||
|
||||
-- Index parts by name to save us from many O(n) FindFirstChild searches
|
||||
local function indexParts(model)
|
||||
local parts = {}
|
||||
for _, child in ipairs(model:GetChildren()) do
|
||||
if child:IsA("BasePart") then
|
||||
local name = child.name
|
||||
-- Index first, mimicing FindFirstChild
|
||||
if not parts[name] then
|
||||
parts[name] = child
|
||||
end
|
||||
end
|
||||
end
|
||||
return parts
|
||||
end
|
||||
|
||||
local function createRigJoints(parts, rig)
|
||||
for _, params in ipairs(rig) do
|
||||
local part0Name, part1Name, attachmentName, limits = unpack(params)
|
||||
local part0 = parts[part0Name]
|
||||
local part1 = parts[part1Name]
|
||||
if part0 and part1 then
|
||||
local a0 = part0:FindFirstChild(attachmentName)
|
||||
local a1 = part1:FindFirstChild(attachmentName)
|
||||
if a0 and a1 and a0:IsA("Attachment") and a1:IsA("Attachment") then
|
||||
-- Our rigs only have one joint per part (connecting each part to it's parent part), so
|
||||
-- we can re-use it if we have to re-rig that part again.
|
||||
local constraint = part1:FindFirstChild(BALL_SOCKET_NAME)
|
||||
if not constraint then
|
||||
constraint = Instance.new("BallSocketConstraint")
|
||||
constraint.Name = BALL_SOCKET_NAME
|
||||
end
|
||||
constraint.Attachment0 = a0
|
||||
constraint.Attachment1 = a1
|
||||
constraint.LimitsEnabled = true
|
||||
constraint.UpperAngle = limits.UpperAngle
|
||||
constraint.TwistLimitsEnabled = true
|
||||
constraint.TwistLowerAngle = limits.TwistLowerAngle
|
||||
constraint.TwistUpperAngle = limits.TwistUpperAngle
|
||||
-- Scale constant torque limit for joint friction relative to gravity and the mass of
|
||||
-- the body part.
|
||||
local gravityScale = workspace.Gravity / REFERENCE_GRAVITY
|
||||
local referenceMass = limits.ReferenceMass
|
||||
local massScale = referenceMass and (part1:GetMass() / referenceMass) or 1
|
||||
local maxTorque = limits.FrictionTorque or DEFAULT_MAX_FRICTION_TORQUE
|
||||
constraint.MaxFrictionTorque = maxTorque * massScale * gravityScale
|
||||
constraint.Parent = part1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function createAdditionalAttachments(parts, attachments)
|
||||
for _, attachmentParams in ipairs(attachments) do
|
||||
local partName, attachmentName, cframe, baseAttachmentName = unpack(attachmentParams)
|
||||
local part = parts[partName]
|
||||
if part then
|
||||
local attachment = part:FindFirstChild(attachmentName)
|
||||
-- Create or update existing attachment
|
||||
if not attachment or attachment:IsA("Attachment") then
|
||||
if baseAttachmentName then
|
||||
local base = part:FindFirstChild(baseAttachmentName)
|
||||
if base and base:IsA("Attachment") then
|
||||
cframe = base.CFrame * cframe
|
||||
end
|
||||
end
|
||||
-- The attachment names are unique within a part, so we can re-use
|
||||
if not attachment then
|
||||
attachment = Instance.new("Attachment")
|
||||
attachment.Name = attachmentName
|
||||
attachment.CFrame = cframe
|
||||
attachment.Parent = part
|
||||
else
|
||||
attachment.CFrame = cframe
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function createNoCollides(parts, noCollides)
|
||||
-- This one's trickier to handle for an already rigged character since a part will have multiple
|
||||
-- NoCollide children with the same name. Having fewer unique names is better for
|
||||
-- replication so we suck it up and deal with the complexity here.
|
||||
|
||||
-- { [Part1] = { [Part0] = true, ... }, ...}
|
||||
local needed = {}
|
||||
-- Following the convention of the Motor6Ds and everything else here we parent the NoCollide to
|
||||
-- Part1, so we start by building the set of Part0s we need a NoCollide with for each Part1
|
||||
for _, namePair in ipairs(noCollides) do
|
||||
local part0Name, part1Name = unpack(namePair)
|
||||
local p0, p1 = parts[part0Name], parts[part1Name]
|
||||
if p0 and p1 then
|
||||
local p0Set = needed[p1]
|
||||
if not p0Set then
|
||||
p0Set = {}
|
||||
needed[p1] = p0Set
|
||||
end
|
||||
p0Set[p0] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Go through NoCollides that exist and remove Part0s from the needed set if we already have
|
||||
-- them covered. Gather NoCollides that aren't between parts in the set for resue
|
||||
local reusableNoCollides = {}
|
||||
for part1, neededPart0s in pairs(needed) do
|
||||
local reusables = {}
|
||||
for _, child in ipairs(part1:GetChildren()) do
|
||||
if child:IsA("NoCollisionConstraint") and child.Name == NO_COLLIDE_NAME then
|
||||
local p0 = child.Part0
|
||||
local p1 = child.Part1
|
||||
if p1 == part1 and neededPart0s[p0] then
|
||||
-- If this matches one that we needed, we don't need to create it anymore.
|
||||
neededPart0s[p0] = nil
|
||||
else
|
||||
-- Otherwise we're free to reuse this NoCollide
|
||||
table.insert(reusables, child)
|
||||
end
|
||||
end
|
||||
end
|
||||
reusableNoCollides[part1] = reusables
|
||||
end
|
||||
|
||||
-- Create the remaining NoCollides needed, re-using old ones if possible
|
||||
for part1, neededPart0s in pairs(needed) do
|
||||
local reusables = reusableNoCollides[part1]
|
||||
for part0, _ in pairs(neededPart0s) do
|
||||
local constraint = table.remove(reusables)
|
||||
if not constraint then
|
||||
constraint = Instance.new("NoCollisionConstraint")
|
||||
end
|
||||
constraint.Name = NO_COLLIDE_NAME
|
||||
constraint.Part0 = part0
|
||||
constraint.Part1 = part1
|
||||
constraint.Parent = part1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function disableMotorSet(model, motorSet)
|
||||
local motors = {}
|
||||
-- Destroy all regular joints:
|
||||
for _, params in ipairs(motorSet) do
|
||||
local part = model:FindFirstChild(params[2])
|
||||
if part then
|
||||
local motor = part:FindFirstChild(params[1])
|
||||
if motor and motor:IsA("Motor6D") then
|
||||
table.insert(motors, motor)
|
||||
motor.Enabled = false
|
||||
end
|
||||
end
|
||||
end
|
||||
return motors
|
||||
end
|
||||
|
||||
function Rigging.createRagdollJoints(model, rigType)
|
||||
local parts = indexParts(model)
|
||||
if rigType == Enum.HumanoidRigType.R6 then
|
||||
createAdditionalAttachments(parts, R6_ADDITIONAL_ATTACHMENTS)
|
||||
createRigJoints(parts, R6_RAGDOLL_RIG)
|
||||
createNoCollides(parts, R6_NO_COLLIDES)
|
||||
elseif rigType == Enum.HumanoidRigType.R15 then
|
||||
createAdditionalAttachments(parts, R15_ADDITIONAL_ATTACHMENTS)
|
||||
createRigJoints(parts, R15_RAGDOLL_RIG)
|
||||
createNoCollides(parts, R15_NO_COLLIDES)
|
||||
else
|
||||
error("unknown rig type", 2)
|
||||
end
|
||||
end
|
||||
|
||||
function Rigging.removeRagdollJoints(model)
|
||||
for _, descendant in pairs(model:GetDescendants()) do
|
||||
-- Remove BallSockets and NoCollides, leave the additional Attachments
|
||||
if (descendant:IsA("BallSocketConstraint") and descendant.Name == BALL_SOCKET_NAME)
|
||||
or (descendant:IsA("NoCollisionConstraint") and descendant.Name == NO_COLLIDE_NAME)
|
||||
then
|
||||
descendant:Destroy()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Rigging.disableMotors(model, rigType)
|
||||
-- Note: We intentionally do not disable the root joint so that the mechanism root of the
|
||||
-- character stays consistent when we break joints on the client. This avoid the need for the client to wait
|
||||
-- for re-assignment of network ownership of a new mechanism, which creates a visible hitch.
|
||||
|
||||
local motors
|
||||
if rigType == Enum.HumanoidRigType.R6 then
|
||||
motors = disableMotorSet(model, R6_MOTOR6DS)
|
||||
elseif rigType == Enum.HumanoidRigType.R15 then
|
||||
motors = disableMotorSet(model, R15_MOTOR6DS)
|
||||
else
|
||||
error("unknown rig type", 2)
|
||||
end
|
||||
|
||||
-- Set the root part to non-collide
|
||||
local rootPart = model.PrimaryPart or model:FindFirstChild("HumanoidRootPart")
|
||||
if rootPart and rootPart:IsA("BasePart") then
|
||||
rootPart.CanCollide = false
|
||||
end
|
||||
|
||||
return motors
|
||||
end
|
||||
|
||||
function Rigging.disableParticleEmittersAndFadeOut(character, duration)
|
||||
if RunService:IsServer() then
|
||||
-- This causes a lot of unnecesarry replicated property changes
|
||||
error("disableParticleEmittersAndFadeOut should not be called on the server.", 2)
|
||||
end
|
||||
|
||||
local descendants = character:GetDescendants()
|
||||
local transparencies = {}
|
||||
for _, instance in pairs(descendants) do
|
||||
if instance:IsA("BasePart") or instance:IsA("Decal") then
|
||||
transparencies[instance] = instance.Transparency
|
||||
elseif instance:IsA("ParticleEmitter") then
|
||||
instance.Enabled = false
|
||||
end
|
||||
end
|
||||
local t = 0
|
||||
while t < duration do
|
||||
-- Using heartbeat because we want to update just before rendering next frame, and not
|
||||
-- block the render thread kicking off (as RenderStepped does)
|
||||
local dt = RunService.Heartbeat:Wait()
|
||||
t = t + dt
|
||||
local alpha = math.min(t / duration, 1)
|
||||
for part, initialTransparency in pairs(transparencies) do
|
||||
part.Transparency = (1 - alpha) * initialTransparency + alpha
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Rigging.easeJointFriction(character, duration)
|
||||
local descendants = character:GetDescendants()
|
||||
-- { { joint, initial friction, end friction }, ... }
|
||||
local frictionJoints = {}
|
||||
for _, v in pairs(descendants) do
|
||||
if v:IsA("BallSocketConstraint") and v.Name == BALL_SOCKET_NAME then
|
||||
local current = v.MaxFrictionTorque
|
||||
-- Keep the torso and neck a little stiffer...
|
||||
local parentName = v.Parent.Name
|
||||
local scale = (parentName == "UpperTorso" or parentName == "Head") and 0.5 or 0.05
|
||||
local nextTorque = current * scale
|
||||
frictionJoints[v] = { v, current, nextTorque }
|
||||
end
|
||||
end
|
||||
local t = 0
|
||||
while t < duration do
|
||||
-- Using stepped because we want to update just before physics sim
|
||||
local _, dt = RunService.Stepped:Wait()
|
||||
t = t + dt
|
||||
local alpha = math.min(t / duration, 1)
|
||||
for _, tuple in pairs(frictionJoints) do
|
||||
local ballSocket, a, b = unpack(tuple)
|
||||
ballSocket.MaxFrictionTorque = (1 - alpha) * a + alpha * b
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Rigging
|
||||
@@ -0,0 +1,37 @@
|
||||
-- Domain list
|
||||
local Urls = {}
|
||||
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
|
||||
local function getBaseDomain(baseUrl)
|
||||
local _, prefixEnd = baseUrl:find("%.")
|
||||
local baseDomain = baseUrl:sub(prefixEnd + 1)
|
||||
|
||||
if baseDomain:sub(-1) ~= "/" then
|
||||
baseDomain = baseDomain .. "/"
|
||||
end
|
||||
return baseDomain
|
||||
end
|
||||
|
||||
local baseUrl = ContentProvider.BaseUrl
|
||||
local baseDomain = getBaseDomain(baseUrl)
|
||||
|
||||
local baseGameUrl = string.format("https://games.%s", baseDomain)
|
||||
local baseRcsUrl = string.format("https://apis.rcs.%s", baseDomain)
|
||||
local baseApisUrl = string.format("https://apis.%s", baseDomain)
|
||||
|
||||
local urlValues = {
|
||||
GAME_URL = baseGameUrl,
|
||||
RCS_URL = baseRcsUrl,
|
||||
APIS_URL = baseApisUrl,
|
||||
}
|
||||
|
||||
setmetatable(Urls, {
|
||||
__newindex = function(t, key, index)
|
||||
end,
|
||||
__index = function(t, index)
|
||||
return urlValues[index]
|
||||
end
|
||||
})
|
||||
|
||||
return Urls
|
||||
Reference in New Issue
Block a user