User:JulienDethurens/Scripts/Heading code: Difference between revisions

From Legacy Roblox Wiki
Jump to navigationJump to search
>JulienDethurens
Created page with "This is code I often put at the top of my code (that's why this page is called "Heading Code", btw). It contains many useful functions, many that I designed for making librar..."
 
>JulienDethurens
Updated.
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
This is code I often put at the top of my code (that's why this page is called "Heading Code", btw).
This is code I often put at the top of my code (that's why this page is called "Heading code", btw).


It contains many useful functions, many that I designed for making libraries easier to create.
It contains many useful functions, many that I designed for making libraries easier to create.
Line 17: Line 17:
*InsertService
*InsertService


Here are the functions defined by it:
{{code|=
local Players = Game:GetService('Players')
local StarterPack = Game:GetService('StarterPack')
local StarterGui = Game:GetService('StarterGui')
local Lighting = Game:GetService('Lighting')
local Debris = Game:GetService('Debris')
local Teams = Game:GetService('Teams')
local BadgeService = Game:GetService('BadgeService')
local InsertService = Game:GetService('InsertService')
local Terrain = Workspace.Terrain
 
local verify_arg -- Since other functions defined before need to use it, this needs to be defined here.
}}


== is_a(<var>value</var>, <var>data_type</var>) ==
== is_a(<var>value</var>, <var>data_type</var>) ==
Line 32: Line 44:
This function returns {{true}} if <var>value</var> is of type <var>data_type</var>. Otherwise, it returns {{false}}.
This function returns {{true}} if <var>value</var> is of type <var>data_type</var>. Otherwise, it returns {{false}}.


It supports the following types: Lua types, Instance types, Enum types, Enum, Color3, BrickColor, Vector2, Vector3, CFrame/CoordinateFrame, UDim, UDim2, RBXScriptSignal, Axes, Faces, Ray.
It supports the following types:
*all the Lua types (recognized by the Lua type function)
*all the Instance types (recognized by the [[IsA]] method
*all the Enum types
*Enum (the enum object itself, accessed through the Enum variable
*Color3
*BrickColor
*Vector2
*Vector3
*CFrame/CoordinateFrame (both names are recognized by the function)
*UDim
*UDim2
*RBXScriptSignal (the real name of events)
*Axes
*Faces
*Ray


Just as a note, you can't use a "fake" value to make it return {{true}} with a value that is similar to a real value of the correct data type. If it says that the value is of the correct data type, then the value <strong>is</strong> of the correct data type.
Just as a note, you can't use a "fake" value to make it return {{true}} with a value that is similar to a real value of the correct data type. If it says that the value is of the correct data type, then the value <strong>is</strong> of the correct data type.
== get_type(<var>value</var>) ==
{| class="wikitable"
|+ Parameters
! Name !! Type !! Description
|-
|value ||      || The value you want to get the type of.
|}
This function returns the most specific type it can get for a certain value. It supports all the types supported by the '''is_a''' function.
== verify_arg(<var>value</var>, <var>data_type</var>, <var>arg_num</var>, <var>func_name</var>, <var>optional</var>) ==
{| class="wikitable"
|+ Parameters
! Name      !! Type    !! Description
|-
| value    ||        || The value you want the verify the type of.
|-
| data_type || string  || The data type the value should be of.
|-
| arg_num  || number  || The № of the arg. Used in the error message.
|-
| func_name || string  || The name of the function. Used in the error message.
|-
| optional  || boolean || Whether the argument is optional or not. If {{true}}, the value {{nil}} will also be accepted.
|}
This function will check if <var>value</var> is of type <var>data_type</var>. If not, it will print an error message:
bad argument #arg_num to func_name (data_type expected, got [the type of the argument, found with the get_type function).
Use it in functions that receive arguments to check if arguments are of the correct type. Using this function can make debugging <strong>a lot</strong> easier (trust me, that's from experience).
== with(<var>instance</var>) ==
{| class="wikitable"
|+ Parameters
! Name    !! Type    !! Description
|-
| instance || Instance || The instance you want to edit.
|}
This function allows you to edit the properties and the hierarchy of a certain object. Because I am too lazy to explain exactly how it works, and because you already have the code anyways, I will only give you an example:


{{code|=
{{code|=
local part with (Instance.new('Part')) {
Name = "Wall";
Position = Vector3.new(10, 50, 10);
with (Instance.new('Fire')) {
Heat = 10;
Size = 50;
};
}
}}
That code creates a part, sets its name to "Wall", sets its position to a certain Vector3, creates a Fire object in it, sets some of the properties of that Fire object, and stores the part in the <var>part</var> variable.
== recurse(<var>object</var>, <var>func</var>) ==
{| class="wikitable"
|+ Parameters
! Name  !! Type    !! Description
|-
| object || Instance || The object you want to call a function on all the descendants of.
|-
| func  || function || The function you want to call on all the descendants of the object.
|}
This function calls <var>func</var> on all the descendants of <var>object</var>, including <var>object</var> itself.
== The Code ==
Here is the code:
{{code|=
local Players = game:GetService('Players')
local StarterPack = game:GetService('StarterPack')
local StarterGui = game:GetService('StarterGui')
local Lighting = game:GetService('Lighting')
local Debris = game:GetService('Debris')
local Teams = game:GetService('Teams')
local BadgeService = game:GetService('BadgeService')
local InsertService = game:GetService('InsertService')
local Terrain = Workspace.Terrain
local function is_a(value, data_type)
local function is_a(value, data_type)
-- Supported types: Lua types, Instance types, Enum types, Enum, Color3, BrickColor, Vector2, Vector3, CFrame/CoordinateFrame, UDim, UDim2, RBXScriptSignal, Axes, Faces, Ray
-- Supported types: Lua types, Instance types, Enum types, Enum, Color3, BrickColor, Vector2, Vector3, CFrame/CoordinateFrame, UDim, UDim2, RBXScriptSignal, Axes, Faces, Ray
Line 130: Line 72:


-- Here is a nice collection of bad practices, ugly hacks, and other things you should never use, but that you don't have the choice of using, because of ROBLOX's lack of an official way to distinguish data types:
-- Here is a nice collection of bad practices, ugly hacks, and other things you should never use, but that you don't have the choice of using, because of ROBLOX's lack of an official way to distinguish data types:
data_type = verify_arg(data_type, 'string', "data_type")
if type(value) == data_type then return true end -- Lua types
if type(value) == data_type then return true end -- Lua types
if pcall(function() assert(game.IsA(value, data_type)) end) then return true end -- Instance types
if pcall(function() assert(Game.IsA(value, data_type)) end) then return true end -- Instance types
if pcall(function() assert(Enum[data_type]) end) then -- Enum types
if pcall(function() assert(Enum[data_type]) end) then -- Enum types
for _, enum in next, Enum[data_type]:GetEnumItems() do
for _, enum in next, Enum[data_type]:GetEnumItems() do
Line 157: Line 100:
elseif data_type == 'Enum' and pcall(Enum.Material.GetEnumItems, value) then return true -- Enum
elseif data_type == 'Enum' and pcall(Enum.Material.GetEnumItems, value) then return true -- Enum
elseif data_type == 'RBXScriptSignal' then
elseif data_type == 'RBXScriptSignal' then
local _, connection = pcall(function() return game.AllowedGearTypeChanged.connect(value) end)
local _, connection = pcall(function() return Game.AllowedGearTypeChanged.connect(value) end)
if _ and connection then
if _ and connection then
connection:disconnect()
connection:disconnect()
Line 165: Line 108:
return false
return false
end
end
}}
== cpp_is_a(<var>value</var>, <var>data_type</var>) ==
{| class="wikitable"
|+ Parameters
! Name      !! Type  !! Description
|-
| value    ||        || The value you want to check the type of.
|-
| data_type || string || The type you want to check if the value is of.
|}
This function is like the is_a function, but is used with the C++ types used by ROBLOX methods and properties. It should be used to know if a certain value would be accepted by a method or a property.
It supports the following types:
*int
*double
*bool
*string
*float
{{code|=
local function cpp_is_a(value, data_type)
-- Same as is_a, but for methods and properties. Only supports basic types.
-- Supports: int, double, bool, string, float
-- Note: this function should be used to know if it is safe to send an argument to a method or a property, as it will also return true for values that will be automatically coerced by ROBLOX.
data_type = verify_arg(data_type, 'string', "data_type")
if data_type == 'int' then
if pcall(function() Instance.new('IntValue').Value = value end) then
return true
end
elseif data_type == 'double' then
if pcall(function() Instance.new('NumberValue').Value = value end) then
return true
end
elseif data_type == 'bool' then
if pcall(function() Instance.new('BoolValue').Value = value end) then
return true
end
elseif data_type == 'string' then
if pcall(function() Instance.new('StringValue').Value = value end) then
return true
end
elseif data_type == 'float' then
if pcall(function() Instance.new('ClickDetector').MaxActivationDistance = value end) then
return true
end
end
return false
end
}}
== get_type(<var>value</var>) ==


{| class="wikitable"
|+ Parameters
! Name  !! Type !! Description
|-
| value ||      || The value you want to get the type of.
|}
This function returns the most specific type it can get for a certain value. It supports all the types supported by the '''is_a''' function.
{{code|=
local function get_type(value)
local function get_type(value)
-- Returns the most specific type it can return. Supports the same types as the is_a function, except the enum types.
-- Returns the most specific type it can return. Supports the same types as the is_a function, except the enum types.
Line 184: Line 192:
end
end
end
end
}}


local function verify_arg(value, data_type, arg_num, func_name, optional)
== verify_arg(<var>value</var>, <var>data_type</var>, <var>arg_name</var>, <var>optional</var>) ==
-- Makes the function that called the calling function error, with an error message relating to a wrong type. Supports the same types as the is_a function.
 
{| class="wikitable"
|+ Parameters
! Name      !! Type    !! Description
|-
| value    ||        || The value you want the verify the type of.
|-
| data_type || string  || The data type the value should be of.
|-
| arg_name  || string  || The name of the argument.
|-
| optional  || boolean || Whether the argument can be nil or not.
|}
 
This function will check if <var>value</var> is of type <var>data_type</var>. If not, it will print an error message relating to the type.
 
Use it in functions that receive arguments to check if arguments are of the correct type. Using this function can make debugging <strong>a lot</strong> easier (trust me, that's from experience).
 
{{code|=
function verify_arg(value, data_type, arg_name, optional)
-- Makes the function that called the calling function error, with an error message relating to a wrong type. Supports all the types supported by the is_a and the cpp_is_a functions.
-- Also supports coercion for the number and string types.
-- Also supports coercion for the number and string types.
-- Returns the value, as it might be automatically converted if a coercion has been done.
-- Returns the value, as it might be automatically converted if a coercion has been done.
if type(data_type) ~= 'string' then error("bad argument #2 to 'verify_arg' (string expected, got " .. type(data_type) .. ")", 2) end
if type(data_type) ~= 'string' then error("bad 'data_type' argument (string expected, got " .. get_type(data_type) .. ")", 2) end
if tonumber(arg_num) then
if type(arg_name) ~= 'string' then error("bad 'arg_name' argument (string expected, got " .. get_type(arg_name) .. ")") end
arg_num = tonumber(arg_num)
else
error("bad argument #3 to 'verify_arg' (number expected, got " .. type(arg_num) .. ")")
end
if type(func_name) ~= 'string' then error("bad argument #4 to 'verify_arg' (string expected, got " .. type(func_name) .. ")") end


if optional and value == nil then
if optional and value == nil then
return value
return value
elseif is_a(value, data_type) then
elseif type(value) == data_type then
return value
elseif is_a(value, data_type) or cpp_is_a(value, data_type) then
return value
return value
elseif data_type == 'number' and tonumber(value) then
elseif data_type == 'number' and tonumber(value) then
Line 206: Line 232:
return tostring(value)
return tostring(value)
else
else
error("bad argument #" .. arg_num .. " to " .. func_name .. " (" .. data_type .. " expected, got " .. get_type(value) .. ")", 3)
error("bad '" .. arg_name .. "'" .. (optional and " optional" or "") .. " argument (" .. data_type .. " expected, got " .. get_type(value) .. ")", 3)
end
end
end
end
}}
== modify(<var>instance</var>, <var>t</var>) ==
{| class="wikitable"
|+ Parameters
! Name    !! Type    !! Description
|-
| instance || Instance || The instance you want to edit.
|-
| t        || table    || The changes  you want to make to <var>instance</var>.
|}
This function allows you to edit the properties and the hierarchy of a certain object. Because I am too lazy to explain exactly how it works, and because you already have the code anyways, I will only give you an example:
{{code|=
local part = modify(Instance.new('Part'), {
Name = "Wall";
Position = Vector3.new(10, 50, 10);
with (Instance.new('Fire'), {
Heat = 10;
Size = 50;
});
})
}}


local function with(instance)
That code creates a part, sets its name to "Wall", sets its position to a certain Vector3, creates a Fire object in it, sets some of the properties of that Fire object, and stores the part in the <var>part</var> variable.
verify_arg(instance, 'Instance', 1, "with")
 
return function(t)
{{code|=
if type(t) ~= 'table' then error("bad argument #1 (table expected, got " .. get_type(t) .. ")", 2) end -- The error message is different, it has no function name, so I can't use verify_arg.
local function modify(instance, t)
for key, value in next, t do
instance = verify_arg(instance, 'Instance', "instance")
if type(key) == 'number' then
t = verify_arg(t, 'table', "t")
value.Parent = instance
for key, value in next, t do
else
if type(key) == 'number' then
instance[key] = value
value.Parent = instance  
end
else
instance[key] = value
end
end
return instance
end
end
return instance
end
end
}}
== call_on_descendants(<var>instance</var>, <var>func</var>) ==
{| class="wikitable"
|+ Parameters
! Name    !! Type    !! Description
|-
| instance || Instance || The object you want to call a function on all the descendants of.
|-
| func    || function || The function you want to call on all the descendants of the object.
|}


local function recurse(object, func)
This function calls <var>func</var> on all the descendants of <var>instance</var>, including <var>instance</var> itself.
-- Calls 'func' on 'object' and all its descendants, with the object or descendant as argument.
 
verify_arg(object, 'Instance', 1, "func")
{{code|=
verify_arg(func, 'function', 2, "func")
local function call_on_descendants(instance, func)
func(object)
-- Calls 'func' on 'instance' and all its descendants, with the instance or descendant as argument.
for _, child in next, object:GetChildren() do
instance = verify_arg(instance, 'Instance', "instance")
recurse(child, func)
func = verify_arg(func, 'function', "func")
func(instance)
for _, child in next, instance:GetChildren() do
call_on_descendants(child, func)
end
end
end
end
|linenumbers=true
}}
}}


== Additions ==
== get_nearest_ancestor(<var>instance</var>, <var>class_name</var>) ==
 
{| class="wikitable"
|+ Parameters
! Name      !! Type    !! Description
|-
| instance  || Instance || The object of which you want to find an ancestor.
|-
| class_name || string  || The class name of the ancestor you want to find.
|}
 
This function returns the nearest ancestor of <var>instance</var> of type <var>class_name</var> in ascending order.
 
{{code|=
local function get_nearest_ancestor(instance, class_name)
-- Returns the nearest ancestor of a certain instance which is of a certain type.
instance = verify_arg(instance, 'Instance', "instance")
class_name = verify_arg(class_name, 'string', "class_name")
local ancestor = instance
repeat
ancestor = ancestor.Parent
if ancestor == nil then
return nil
end
until ancestor:IsA(class_name)
return ancestor
end
}}
 
== get_character(<var>descendant</var>) ==
 
{| class="wikitable"
|+ Parameters
! Name      !! Type    !! Description
|-
| descendant  || Instance || The descendant of the character you want to get.
|}
 
This function returns the character a descendant is a part of.
 
{{code|=
local function get_character(descendant)
-- Returns a character from one of its descendants.
descendant = verify_arg(descendant, 'Instance', "descendant")
local character = descendant
repeat
if character.Parent then
character = character.Parent
else
return nil
end
until Players:GetPlayerFromCharacter(character)
return character
end
}}
 
== show_message(<var>text</var>, <var>lifetime</var>) ==
 
{| class="wikitable"
|+ Parameters
! Name    !! Type  !! Description
|-
| text    || string || The text you want to show in the message.
|-
| lifetime || number || The time, in seconds, during which the message must be displayed.
|}
 
This function shows a message and removes it after a certain time.
 
{{code|=
local function show_message(text, lifetime)
-- Shows a message for a certain time, which is set to be 3 seconds by default.
text = verify_arg(text, 'string', "text")
lifetime = verify_arg(lifetime, 'number', "lifetime")
local message = Instance.new('Message')
message.Text = text
Debris:AddItem(message, lifetime or 3)
end
}}
 
== show_hint(<var>text</var>, <var>lifetime</var>) ==
 
{| class="wikitable"
|+ Parameters
! Name    !! Type  !! Description
|-
| text    || string || The text you want to show in the hint.
|-
| lifetime || number || The time, in seconds, during which the hint must be displayed.
|}
 
This function shows a hint and removes it after a certain time.
 
{{code|=
local function show_hint(text, lifetime)
-- Shows a hint for a certain time, which is set to be 3 seconds by default.
text = verify_arg(text, 'string', "text")
lifetime = verify_arg(lifetime, 'number', "lifetime")
local hint = Instance.new('Hint')
hint.Text = text
Debris:AddItem(hint, lifetime or 3)
end
}}


I did not put all of the code in the above section. I ignored the class system and the predefined classes I usually use. That is because I am going to use the class system present in the [[RbxUtility]] library as soon as it will be released. Not that I believe it is better than my own class system, I find mine to be a lot better, but using the one in the [[RbxUtility]] library will save me the trouble of including it at the top of all of my scripts, as well as the trouble of updating it and making sure it is kept up to date. It will also make my code actually use a class system that is recognized by ROBLOX and that more people know of, therefore making my code easier to understand for people who have never seen my class system used anywhere before.
== Entire code ==


But, in case you're interested, here is the whole code, including the class system and the classes that I usually use with it:
Here is the whole code:


{{code|=
{{code|=
local Players = game:GetService('Players')
local Players = Game:GetService('Players')
local StarterPack = game:GetService('StarterPack')
local StarterPack = Game:GetService('StarterPack')
local StarterGui = game:GetService('StarterGui')
local StarterGui = Game:GetService('StarterGui')
local Lighting = game:GetService('Lighting')
local Lighting = Game:GetService('Lighting')
local Debris = game:GetService('Debris')
local Debris = Game:GetService('Debris')
local Teams = game:GetService('Teams')
local Teams = Game:GetService('Teams')
local BadgeService = game:GetService('BadgeService')
local BadgeService = Game:GetService('BadgeService')
local InsertService = game:GetService('InsertService')
local InsertService = Game:GetService('InsertService')
local Terrain = Workspace.Terrain
local Terrain = Workspace.Terrain
local verify_arg -- Since other functions defined before need to use it, this needs to be defined here.


local function is_a(value, data_type)
local function is_a(value, data_type)
Line 262: Line 432:


-- Here is a nice collection of bad practices, ugly hacks, and other things you should never use, but that you don't have the choice of using, because of ROBLOX's lack of an official way to distinguish data types:
-- Here is a nice collection of bad practices, ugly hacks, and other things you should never use, but that you don't have the choice of using, because of ROBLOX's lack of an official way to distinguish data types:
data_type = verify_arg(data_type, 'string', "data_type")
if type(value) == data_type then return true end -- Lua types
if type(value) == data_type then return true end -- Lua types
if pcall(function() assert(game.IsA(value, data_type)) end) then return true end -- Instance types
if pcall(function() assert(Game.IsA(value, data_type)) end) then return true end -- Instance types
if pcall(function() assert(Enum[data_type]) end) then -- Enum types
if pcall(function() assert(Enum[data_type]) end) then -- Enum types
for _, enum in next, Enum[data_type]:GetEnumItems() do
for _, enum in next, Enum[data_type]:GetEnumItems() do
Line 289: Line 460:
elseif data_type == 'Enum' and pcall(Enum.Material.GetEnumItems, value) then return true -- Enum
elseif data_type == 'Enum' and pcall(Enum.Material.GetEnumItems, value) then return true -- Enum
elseif data_type == 'RBXScriptSignal' then
elseif data_type == 'RBXScriptSignal' then
local _, connection = pcall(function() return game.AllowedGearTypeChanged.connect(value) end)
local _, connection = pcall(function() return Game.AllowedGearTypeChanged.connect(value) end)
if _ and connection then
if _ and connection then
connection:disconnect()
connection:disconnect()
return true
end
end
return false
end
local function cpp_is_a(value, data_type)
-- Same as is_a, but for methods and properties. Only supports basic types.
-- Supports: int, double, bool, string, float
-- Note: this function should be used to know if it is safe to send an argument to a method or a property, as it will also return true for values that will be automatically coerced by ROBLOX.
data_type = verify_arg(data_type, 'string', "data_type")
if data_type == 'int' then
if pcall(function() Instance.new('IntValue').Value = value end) then
return true
end
elseif data_type == 'double' then
if pcall(function() Instance.new('NumberValue').Value = value end) then
return true
end
elseif data_type == 'bool' then
if pcall(function() Instance.new('BoolValue').Value = value end) then
return true
end
elseif data_type == 'string' then
if pcall(function() Instance.new('StringValue').Value = value end) then
return true
end
elseif data_type == 'float' then
if pcall(function() Instance.new('ClickDetector').MaxActivationDistance = value end) then
return true
return true
end
end
Line 317: Line 517:
end
end


local function verify_arg(value, data_type, arg_num, func_name, optional)
function verify_arg(value, data_type, arg_name, optional)
-- Makes the function that called the calling function error, with an error message relating to a wrong type. Supports the same types as the is_a function.
-- Makes the function that called the calling function error, with an error message relating to a wrong type. Supports all the types supported by the is_a and the cpp_is_a functions.
-- Also supports coercion for the number and string types.
-- Also supports coercion for the number and string types.
-- Returns the value, as it might be automatically converted if a coercion has been done.
-- Returns the value, as it might be automatically converted if a coercion has been done.
if type(data_type) ~= 'string' then error("bad argument #2 to 'verify_arg' (string expected, got " .. type(data_type) .. ")", 2) end
if type(data_type) ~= 'string' then error("bad 'data_type' argument (string expected, got " .. get_type(data_type) .. ")", 2) end
if tonumber(arg_num) then
if type(arg_name) ~= 'string' then error("bad 'arg_name' argument (string expected, got " .. get_type(arg_name) .. ")") end
arg_num = tonumber(arg_num)
else
error("bad argument #3 to 'verify_arg' (number expected, got " .. type(arg_num) .. ")")
end
if type(func_name) ~= 'string' then error("bad argument #4 to 'verify_arg' (string expected, got " .. type(func_name) .. ")") end


if optional and value == nil then
if optional and value == nil then
return value
return value
elseif is_a(value, data_type) then
elseif type(value) == data_type then
return value
elseif is_a(value, data_type) or cpp_is_a(value, data_type) then
return value
return value
elseif data_type == 'number' and tonumber(value) then
elseif data_type == 'number' and tonumber(value) then
Line 338: Line 535:
return tostring(value)
return tostring(value)
else
else
error("bad argument #" .. arg_num .. " to " .. func_name .. " (" .. data_type .. " expected, got " .. get_type(value) .. ")", 3)
error("bad '" .. arg_name .. "'" .. (optional and " optional" or "") .. " argument (" .. data_type .. " expected, got " .. get_type(value) .. ")", 3)
end
end
end
end


local function with(instance)
local function modify(instance, t)
verify_arg(instance, 'Instance', 1, "with")
instance = verify_arg(instance, 'Instance', "instance")
return function(t)
t = verify_arg(t, 'table', "t")
if type(t) ~= 'table' then error("bad argument #1 (table expected, got " .. get_type(t) .. ")", 2) end -- The error message is different, it has no function name, so I can't use verify_arg.
for key, value in next, t do
for key, value in next, t do
if type(key) == 'number' then
if type(key) == 'number' then
value.Parent = instance  
value.Parent = instance
else
else
instance[key] = value
instance[key] = value
end
end
end
return instance
end
end
return instance
end
end


local function recurse(object, func)
local function call_on_descendants(instance, func)
-- Calls 'func' on 'object' and all its descendants, with the object or descendant as argument.
-- Calls 'func' on 'instance' and all its descendants, with the instance or descendant as argument.
verify_arg(object, 'Instance', 1, "func")
instance = verify_arg(instance, 'Instance', "instance")
verify_arg(func, 'function', 2, "func")
func = verify_arg(func, 'function', "func")
func(object)
func(instance)
for _, child in next, object:GetChildren() do
for _, child in next, instance:GetChildren() do
recurse(child, func)
call_on_descendants(child, func)
end
end
end
end


local function create(constructor, ...)
local function get_nearest_ancestor(instance, class_name)
-- Sometimes, there will be only one object of a certain kind. In such cases, creating a class would be useless.
-- Returns the nearest ancestor of a certain instance which is of a certain type.
verify_arg(constructor, 'function', 1, "create")
instance = verify_arg(instance, 'Instance', "instance")
local content = {} -- This will by default be the content of the object, unless the user decides to change the __index metamethod of his object.
class_name = verify_arg(class_name, 'string', "class_name")
local metatable = setmetatable({__index = content, __metatable = false}, locked_metatable) -- Creates a locked metatable. I'm not going to let the user edit his object's metatable's metatable...
local ancestor = instance
local returned_values = {constructor(content, metatable, ...)} -- Execute the constructor. Gives a content table, but also the metatable, for further customization.
repeat
local object = newproxy(true)
ancestor = ancestor.Parent
metatable.__metatable = false -- Incase the user edited the __metatable field.
if ancestor == nil then
for key, value in next, metatable do
return nil
if type(key) == 'string' and key:sub(1, 2) == "__" then
getmetatable(object)[key] = value
end
end
end
until ancestor:IsA(class_name)
return object, unpack(returned_values)
return ancestor
end
end


local class, new
local function get_character(descendant)
 
-- Returns a character from one of its descendants.
do -- Only the class and new functions are supposed to have access to the constructors table.
descendant = verify_arg(descendant, 'Instance', "descendant")
local locked_metatable = {__metatable = false} -- To avoid having to create a new table everytime.
local character = descendant
local constructors = {}
repeat
function class(class_name, constructor)
if character.Parent then
class_name = verify_arg(class_name, 'string', 1, "class")
character = character.Parent
verify_arg(constructor, 'function', 2, "class")
else
if constructors[class_name] then error("bad argument #1 to 'class' (can't overwrite existing class')", 2) end
return nil
constructors[class_name] = constructor
end
function new(class_name, ...)
if not constructors[class_name] then error("bad argument #1 to 'new' (invalid class name)", 2) end
local content = {
ClassName = class_name;
} -- This will by default be the content of the object, unless the user decides to change the __index metamethod of his object.
local metatable = setmetatable({__index = content, __metatable = false}, locked_metatable) -- Creates a locked metatable. I'm not going to let the user edit his object's metatable's metatable...
local returned_values = {constructors[class_name](content, metatable, ...)} -- Execute the constructor. Gives a content table, but also the metatable, for further customization.
local object = newproxy(true)
metatable.__metatable = false -- Incase the user edited the __metatable field.
for key, value in next, metatable do
-- Transfer the metatable to the object's metatable.
if type(key) == 'string' and key:sub(1, 2) == "__" then
getmetatable(object)[key] = value
end
end
end
return object, unpack(returned_values)
until Players:GetPlayerFromCharacter(character)
end
return character
end
end


-- Requires the basic functions.
local function show_message(text, lifetime)
-- Shows a message for a certain time, which is set to be 3 seconds by default.
text = verify_arg(text, 'string', "text")
lifetime = verify_arg(lifetime, 'number', "lifetime")
local message = Instance.new('Message')
message.Text = text
Debris:AddItem(message, lifetime or 3)
end


class('Connection', function(content, listeners, func)
local function show_hint(text, lifetime)
function content:Delete()
-- Shows a hint for a certain time, which is set to be 3 seconds by default.
for key in next, content do
text = verify_arg(text, 'string', "text")
content[key] = nil
lifetime = verify_arg(lifetime, 'number', "lifetime")
end
local hint = Instance.new('Hint')
content, listeners, func = nil
hint.Text = text
end
Debris:AddItem(hint, lifetime or 3)
 
end
function content:Disconnect()
listeners[func] =  nil
content:Delete()
end
end)
 
class('Signal', function(content)
local listeners = {}
 
function content:Delete()
listeners = nil
for key in next, content do
content[key] = nil
end
content = nil
end
 
function content:Connect(func)
check_arg(func, 'function', 1, "Connect")
listeners[func] = true
return new('Connection', listeners, func)
end
return function(...)
for listener in next, listeners do
listener(...)
end
end
end)
 
class('Value', function(content, metatable, required_type, accept_nil)
if required_type ~= nil and type(required_type) ~= 'string' then
error("bad argument #1 (string expected, got " .. get_type(required_type) .. ")", 3) -- Yes, 3. It isn't the class system that did a mistake, it is the function that called the class system. Therefore, 3.
end
local fire
content.Changed, fire = new('Signal')
content.Value = nil
function metatable.__newindex(_, index, value)
if index ~= "Value" then
error("attempt to index a userdata value", 2)
elseif not is_a(value, required_type) and not (value == nil and accept_nil) then
error(required_type .. " expected, got " .. get_type(value), 2)
else
content.Value = value
fire(value)
end
end
end)
 
class('ReadOnlyValue', function(content, _, required_type, accept_nil)
if required_type ~= nil and type(required_type) ~= 'string' then
error("bad argument #1 (string expected, got " .. get_type(required_type) .. ")", 3) -- Yes, 3. It isn't the class system that did a mistake, it is the function that called the class system. Therefore, 3.
end
local fire
content.Changed, fire = new('Signal')
content.Value = nil
return function(value)
if is_a(value, required_type) or (value == nil and accept_nil) then
content.Value = value
fire(value)
else
error(required_type .. " expected, got " .. get_type(value), 2)
end
content.Value = value
fire(value)
end
end)
 
class('Collector', function(content)
local items = setmetatable({}, {__mode = 'kv'})
 
function content:Delete()
for key in next, items do
items[key] = nil
end
for key in next, content do
content[key] = nil
end
items, content = nil
end
 
function content:AddInstance(instance)
check_arg(instance, 'Instance', 1, "AddInstance")
table.insert(items, instance)
end
 
function content:AddConnection(connection)
-- Note: this function might get fooled by a fake connection, since there is no way to know for sure whether a value is a RBXScriptConnection without disconnecting it. And, anyways, I also want it to work with my Signal class, so anyways...
if not (type(connection) == 'userdata' and pcall(function() assert(type(connection.disconnect) == 'function' or type(connection.Disconnect) == 'function') end)) then
error("bad argument #1 to 'AddConnection' (connection expected, got " .. get_type(connection) .. ")", 2)
end
table.insert(items, connection)
end
 
function content:Clear()
for _, item in next, items do
if is_a(item, 'Instance') then
pcall(item.Destroy, item)
else
pcall(function() item:disconnect() end)
pcall(function() item:Disconnect() end)
end
end
end
end)
|linenumbers=true
}}
}}

Latest revision as of 00:37, 25 April 2012

This is code I often put at the top of my code (that's why this page is called "Heading code", btw).

It contains many useful functions, many that I designed for making libraries easier to create.

Variables

The code creates a variable that makes accessing the Terrain object easier, that variable is called "Terrain".

It also creates many variables for some services that I commonly use:

  • Players
  • StarterPack
  • StarterGui
  • Lighting
  • Debris
  • Teams
  • BadgeService
  • InsertService
local Players = Game:GetService('Players')
local StarterPack = Game:GetService('StarterPack')
local StarterGui = Game:GetService('StarterGui')
local Lighting = Game:GetService('Lighting')
local Debris = Game:GetService('Debris')
local Teams = Game:GetService('Teams')
local BadgeService = Game:GetService('BadgeService')
local InsertService = Game:GetService('InsertService')
local Terrain = Workspace.Terrain

local verify_arg -- Since other functions defined before need to use it, this needs to be defined here.

is_a(value, data_type)

Parameters
Name Type Description
value The value you want to check the type of.
data_type string The type you want to check for.

This function returns true if value is of type data_type. Otherwise, it returns false.

It supports the following types:

  • all the Lua types (recognized by the Lua type function)
  • all the Instance types (recognized by the IsA method
  • all the Enum types
  • Enum (the enum object itself, accessed through the Enum variable
  • Color3
  • BrickColor
  • Vector2
  • Vector3
  • CFrame/CoordinateFrame (both names are recognized by the function)
  • UDim
  • UDim2
  • RBXScriptSignal (the real name of events)
  • Axes
  • Faces
  • Ray

Just as a note, you can't use a "fake" value to make it return true with a value that is similar to a real value of the correct data type. If it says that the value is of the correct data type, then the value is of the correct data type.

local function is_a(value, data_type)
	-- Supported types: Lua types, Instance types, Enum types, Enum, Color3, BrickColor, Vector2, Vector3, CFrame/CoordinateFrame, UDim, UDim2, RBXScriptSignal, Axes, Faces, Ray
	-- Will return false if the type is not supported, even though the value might be of that type.
	-- This function can not be fooled by a fake value. If it says the value is a ClickDetector, then it IS a ClickDetector.
	-- This function uses a variety of ugly hacks that were found by JulienDethurens.
	-- I wish ROBLOX just provided an official way to do this, I wouldn't have to use lots of unrealiable ways to get the info I need... :/

	-- Here is a nice collection of bad practices, ugly hacks, and other things you should never use, but that you don't have the choice of using, because of ROBLOX's lack of an official way to distinguish data types:
	data_type = verify_arg(data_type, 'string', "data_type")
	if type(value) == data_type then return true end -- Lua types
	if pcall(function() assert(Game.IsA(value, data_type)) end) then return true end -- Instance types
	if pcall(function() assert(Enum[data_type]) end) then -- Enum types
		for _, enum in next, Enum[data_type]:GetEnumItems() do
			if value == enum then
				return true
			end
		end
	elseif pcall(Enum.Material.GetEnumItems, value) then
		for _, enum in next, value:GetEnumItems() do
			if value == enum then
				return true
			end
		end
	end
	if data_type == 'Color3' and pcall(function() Instance.new('Color3Value').Value = value end) then return true -- Color3
	elseif data_type == 'BrickColor' and pcall(function() Instance.new('BrickColorValue').Value = value end) then return true -- BrickColor
	elseif data_type == 'Vector2' and pcall(function() return Vector2.new() + value end) then return true -- Vector2
	elseif data_type == 'Vector3' and pcall(function() Instance.new('Vector3Value').Value = value end) then return true -- Vector3
	elseif (data_type == 'CFrame' or data_type == 'CoordinateFrame') and pcall(function() Instance.new('CFrameValue').Value = value end) then return true -- CFrame
	elseif data_type == 'UDim' and pcall(function() return UDim.new() + value end) then return true -- UDim
	elseif data_type == 'UDim2' and pcall(function() Instance.new('Frame').Position = value end) then return true -- UDim2
	elseif data_type == 'Ray' and pcall(function() Ray.new(Vector3.new(), Vector3.new()).Distance(value, Vector3.new()) end) then return true -- Ray
	elseif data_type == 'Axes' and pcall(function() Instance.new('ArcHandles').Axes = value end) then return true -- Axes
	elseif data_type == 'Faces' and pcall(function() Instance.new('Handles').Faces = value end) then return true -- Faces
	elseif data_type == 'Enum' and pcall(Enum.Material.GetEnumItems, value) then return true -- Enum
	elseif data_type == 'RBXScriptSignal' then
		local _, connection = pcall(function() return Game.AllowedGearTypeChanged.connect(value) end)
		if _ and connection then
			connection:disconnect()
			return true
		end
	end
	return false
end

cpp_is_a(value, data_type)

Parameters
Name Type Description
value The value you want to check the type of.
data_type string The type you want to check if the value is of.

This function is like the is_a function, but is used with the C++ types used by ROBLOX methods and properties. It should be used to know if a certain value would be accepted by a method or a property.

It supports the following types:

  • int
  • double
  • bool
  • string
  • float
local function cpp_is_a(value, data_type)
	-- Same as is_a, but for methods and properties. Only supports basic types.
	-- Supports: int, double, bool, string, float
	-- Note: this function should be used to know if it is safe to send an argument to a method or a property, as it will also return true for values that will be automatically coerced by ROBLOX.
	data_type = verify_arg(data_type, 'string', "data_type")
	if data_type == 'int' then
		if pcall(function() Instance.new('IntValue').Value = value end) then
			return true
		end
	elseif data_type == 'double' then
		if pcall(function() Instance.new('NumberValue').Value = value end) then
			return true
		end
	elseif data_type == 'bool' then
		if pcall(function() Instance.new('BoolValue').Value = value end) then
			return true
		end
	elseif data_type == 'string' then
		if pcall(function() Instance.new('StringValue').Value = value end) then
			return true
		end
	elseif data_type == 'float' then
		if pcall(function() Instance.new('ClickDetector').MaxActivationDistance = value end) then
			return true
		end
	end
	return false
end

get_type(value)

Parameters
Name Type Description
value The value you want to get the type of.

This function returns the most specific type it can get for a certain value. It supports all the types supported by the is_a function.

local function get_type(value)
	-- Returns the most specific type it can return. Supports the same types as the is_a function, except the enum types.
	if is_a(value, 'Instance') then return value.ClassName
	elseif is_a(value, 'Enum') then return 'Enum'
	elseif is_a(value, 'Color3') then return 'Color3'
	elseif is_a(value, 'BrickColor') then return 'BrickColor'
	elseif is_a(value, 'Vector2') then return 'Vector2'
	elseif is_a(value, 'Vector3') then return 'Vector3'
	elseif is_a(value, 'CFrame') then return 'CFrame'
	elseif is_a(value, 'UDim') then return 'UDim'
	elseif is_a(value, 'UDim2') then return 'UDim2'
	elseif is_a(value, 'Ray') then return 'Ray'
	elseif is_a(value, 'Axes') then return 'Axes'
	elseif is_a(value, 'Faces') then return 'Faces'
	elseif is_a(value, 'RBXScriptSignal') then return 'RBXScriptSignal'
	else return type(value)
	end
end

verify_arg(value, data_type, arg_name, optional)

Parameters
Name Type Description
value The value you want the verify the type of.
data_type string The data type the value should be of.
arg_name string The name of the argument.
optional boolean Whether the argument can be nil or not.

This function will check if value is of type data_type. If not, it will print an error message relating to the type.

Use it in functions that receive arguments to check if arguments are of the correct type. Using this function can make debugging a lot easier (trust me, that's from experience).

function verify_arg(value, data_type, arg_name, optional)
	-- Makes the function that called the calling function error, with an error message relating to a wrong type. Supports all the types supported by the is_a and the cpp_is_a functions.
	-- Also supports coercion for the number and string types.
	-- Returns the value, as it might be automatically converted if a coercion has been done.
	if type(data_type) ~= 'string' then error("bad 'data_type' argument (string expected, got " .. get_type(data_type) .. ")", 2) end
	if type(arg_name) ~= 'string' then error("bad 'arg_name' argument (string expected, got " .. get_type(arg_name) .. ")") end

	if optional and value == nil then
		return value
	elseif type(value) == data_type then
		return value
	elseif is_a(value, data_type) or cpp_is_a(value, data_type) then
		return value
	elseif data_type == 'number' and tonumber(value) then
		return tonumber(value)
	elseif data_type == 'string' and type(value) == 'number' then
		return tostring(value)
	else
		error("bad '" .. arg_name .. "'" .. (optional and " optional" or "") .. " argument (" .. data_type .. " expected, got " .. get_type(value) .. ")", 3)
	end
end

modify(instance, t)

Parameters
Name Type Description
instance Instance The instance you want to edit.
t table The changes you want to make to instance.

This function allows you to edit the properties and the hierarchy of a certain object. Because I am too lazy to explain exactly how it works, and because you already have the code anyways, I will only give you an example:

local part = modify(Instance.new('Part'), {
	Name = "Wall";
	Position = Vector3.new(10, 50, 10);
	with (Instance.new('Fire'), {
		Heat = 10;
		Size = 50;
	});
})

That code creates a part, sets its name to "Wall", sets its position to a certain Vector3, creates a Fire object in it, sets some of the properties of that Fire object, and stores the part in the part variable.

local function modify(instance, t)
	instance = verify_arg(instance, 'Instance', "instance")
	t = verify_arg(t, 'table', "t")
	for key, value in next, t do
		if type(key) == 'number' then
			value.Parent = instance 
		else
			instance[key] = value
		end
	end
	return instance
end

call_on_descendants(instance, func)

Parameters
Name Type Description
instance Instance The object you want to call a function on all the descendants of.
func function The function you want to call on all the descendants of the object.

This function calls func on all the descendants of instance, including instance itself.

local function call_on_descendants(instance, func)
	-- Calls 'func' on 'instance' and all its descendants, with the instance or descendant as argument.
	instance = verify_arg(instance, 'Instance', "instance")
	func = verify_arg(func, 'function', "func")
	func(instance)
	for _, child in next, instance:GetChildren() do
		call_on_descendants(child, func)
	end
end

get_nearest_ancestor(instance, class_name)

Parameters
Name Type Description
instance Instance The object of which you want to find an ancestor.
class_name string The class name of the ancestor you want to find.

This function returns the nearest ancestor of instance of type class_name in ascending order.

local function get_nearest_ancestor(instance, class_name)
	-- Returns the nearest ancestor of a certain instance which is of a certain type.
	instance = verify_arg(instance, 'Instance', "instance")
	class_name = verify_arg(class_name, 'string', "class_name")
	local ancestor = instance
	repeat
		ancestor = ancestor.Parent
		if ancestor == nil then
			return nil
		end
	until ancestor:IsA(class_name)
	return ancestor
end

get_character(descendant)

Parameters
Name Type Description
descendant Instance The descendant of the character you want to get.

This function returns the character a descendant is a part of.

local function get_character(descendant)
	-- Returns a character from one of its descendants.
	descendant = verify_arg(descendant, 'Instance', "descendant")
	local character = descendant
	repeat
		if character.Parent then
			character = character.Parent
		else
			return nil
		end
	until Players:GetPlayerFromCharacter(character)
	return character
end

show_message(text, lifetime)

Parameters
Name Type Description
text string The text you want to show in the message.
lifetime number The time, in seconds, during which the message must be displayed.

This function shows a message and removes it after a certain time.

local function show_message(text, lifetime)
	-- Shows a message for a certain time, which is set to be 3 seconds by default.
	text = verify_arg(text, 'string', "text")
	lifetime = verify_arg(lifetime, 'number', "lifetime")
	local message = Instance.new('Message')
	message.Text = text
	Debris:AddItem(message, lifetime or 3)
end

show_hint(text, lifetime)

Parameters
Name Type Description
text string The text you want to show in the hint.
lifetime number The time, in seconds, during which the hint must be displayed.

This function shows a hint and removes it after a certain time.

local function show_hint(text, lifetime)
	-- Shows a hint for a certain time, which is set to be 3 seconds by default.
	text = verify_arg(text, 'string', "text")
	lifetime = verify_arg(lifetime, 'number', "lifetime")
	local hint = Instance.new('Hint')
	hint.Text = text
	Debris:AddItem(hint, lifetime or 3)
end

Entire code

Here is the whole code:

local Players = Game:GetService('Players')
local StarterPack = Game:GetService('StarterPack')
local StarterGui = Game:GetService('StarterGui')
local Lighting = Game:GetService('Lighting')
local Debris = Game:GetService('Debris')
local Teams = Game:GetService('Teams')
local BadgeService = Game:GetService('BadgeService')
local InsertService = Game:GetService('InsertService')
local Terrain = Workspace.Terrain

local verify_arg -- Since other functions defined before need to use it, this needs to be defined here.

local function is_a(value, data_type)
	-- Supported types: Lua types, Instance types, Enum types, Enum, Color3, BrickColor, Vector2, Vector3, CFrame/CoordinateFrame, UDim, UDim2, RBXScriptSignal, Axes, Faces, Ray
	-- Will return false if the type is not supported, even though the value might be of that type.
	-- This function can not be fooled by a fake value. If it says the value is a ClickDetector, then it IS a ClickDetector.
	-- This function uses a variety of ugly hacks that were found by JulienDethurens.
	-- I wish ROBLOX just provided an official way to do this, I wouldn't have to use lots of unrealiable ways to get the info I need... :/

	-- Here is a nice collection of bad practices, ugly hacks, and other things you should never use, but that you don't have the choice of using, because of ROBLOX's lack of an official way to distinguish data types:
	data_type = verify_arg(data_type, 'string', "data_type")
	if type(value) == data_type then return true end -- Lua types
	if pcall(function() assert(Game.IsA(value, data_type)) end) then return true end -- Instance types
	if pcall(function() assert(Enum[data_type]) end) then -- Enum types
		for _, enum in next, Enum[data_type]:GetEnumItems() do
			if value == enum then
				return true
			end
		end
	elseif pcall(Enum.Material.GetEnumItems, value) then
		for _, enum in next, value:GetEnumItems() do
			if value == enum then
				return true
			end
		end
	end
	if data_type == 'Color3' and pcall(function() Instance.new('Color3Value').Value = value end) then return true -- Color3
	elseif data_type == 'BrickColor' and pcall(function() Instance.new('BrickColorValue').Value = value end) then return true -- BrickColor
	elseif data_type == 'Vector2' and pcall(function() return Vector2.new() + value end) then return true -- Vector2
	elseif data_type == 'Vector3' and pcall(function() Instance.new('Vector3Value').Value = value end) then return true -- Vector3
	elseif (data_type == 'CFrame' or data_type == 'CoordinateFrame') and pcall(function() Instance.new('CFrameValue').Value = value end) then return true -- CFrame
	elseif data_type == 'UDim' and pcall(function() return UDim.new() + value end) then return true -- UDim
	elseif data_type == 'UDim2' and pcall(function() Instance.new('Frame').Position = value end) then return true -- UDim2
	elseif data_type == 'Ray' and pcall(function() Ray.new(Vector3.new(), Vector3.new()).Distance(value, Vector3.new()) end) then return true -- Ray
	elseif data_type == 'Axes' and pcall(function() Instance.new('ArcHandles').Axes = value end) then return true -- Axes
	elseif data_type == 'Faces' and pcall(function() Instance.new('Handles').Faces = value end) then return true -- Faces
	elseif data_type == 'Enum' and pcall(Enum.Material.GetEnumItems, value) then return true -- Enum
	elseif data_type == 'RBXScriptSignal' then
		local _, connection = pcall(function() return Game.AllowedGearTypeChanged.connect(value) end)
		if _ and connection then
			connection:disconnect()
			return true
		end
	end
	return false
end

local function cpp_is_a(value, data_type)
	-- Same as is_a, but for methods and properties. Only supports basic types.
	-- Supports: int, double, bool, string, float
	-- Note: this function should be used to know if it is safe to send an argument to a method or a property, as it will also return true for values that will be automatically coerced by ROBLOX.
	data_type = verify_arg(data_type, 'string', "data_type")
	if data_type == 'int' then
		if pcall(function() Instance.new('IntValue').Value = value end) then
			return true
		end
	elseif data_type == 'double' then
		if pcall(function() Instance.new('NumberValue').Value = value end) then
			return true
		end
	elseif data_type == 'bool' then
		if pcall(function() Instance.new('BoolValue').Value = value end) then
			return true
		end
	elseif data_type == 'string' then
		if pcall(function() Instance.new('StringValue').Value = value end) then
			return true
		end
	elseif data_type == 'float' then
		if pcall(function() Instance.new('ClickDetector').MaxActivationDistance = value end) then
			return true
		end
	end
	return false
end

local function get_type(value)
	-- Returns the most specific type it can return. Supports the same types as the is_a function, except the enum types.
	if is_a(value, 'Instance') then return value.ClassName
	elseif is_a(value, 'Enum') then return 'Enum'
	elseif is_a(value, 'Color3') then return 'Color3'
	elseif is_a(value, 'BrickColor') then return 'BrickColor'
	elseif is_a(value, 'Vector2') then return 'Vector2'
	elseif is_a(value, 'Vector3') then return 'Vector3'
	elseif is_a(value, 'CFrame') then return 'CFrame'
	elseif is_a(value, 'UDim') then return 'UDim'
	elseif is_a(value, 'UDim2') then return 'UDim2'
	elseif is_a(value, 'Ray') then return 'Ray'
	elseif is_a(value, 'Axes') then return 'Axes'
	elseif is_a(value, 'Faces') then return 'Faces'
	elseif is_a(value, 'RBXScriptSignal') then return 'RBXScriptSignal'
	else return type(value)
	end
end

function verify_arg(value, data_type, arg_name, optional)
	-- Makes the function that called the calling function error, with an error message relating to a wrong type. Supports all the types supported by the is_a and the cpp_is_a functions.
	-- Also supports coercion for the number and string types.
	-- Returns the value, as it might be automatically converted if a coercion has been done.
	if type(data_type) ~= 'string' then error("bad 'data_type' argument (string expected, got " .. get_type(data_type) .. ")", 2) end
	if type(arg_name) ~= 'string' then error("bad 'arg_name' argument (string expected, got " .. get_type(arg_name) .. ")") end

	if optional and value == nil then
		return value
	elseif type(value) == data_type then
		return value
	elseif is_a(value, data_type) or cpp_is_a(value, data_type) then
		return value
	elseif data_type == 'number' and tonumber(value) then
		return tonumber(value)
	elseif data_type == 'string' and type(value) == 'number' then
		return tostring(value)
	else
		error("bad '" .. arg_name .. "'" .. (optional and " optional" or "") .. " argument (" .. data_type .. " expected, got " .. get_type(value) .. ")", 3)
	end
end

local function modify(instance, t)
	instance = verify_arg(instance, 'Instance', "instance")
	t = verify_arg(t, 'table', "t")
	for key, value in next, t do
		if type(key) == 'number' then
			value.Parent = instance 
		else
			instance[key] = value
		end
	end
	return instance
end

local function call_on_descendants(instance, func)
	-- Calls 'func' on 'instance' and all its descendants, with the instance or descendant as argument.
	instance = verify_arg(instance, 'Instance', "instance")
	func = verify_arg(func, 'function', "func")
	func(instance)
	for _, child in next, instance:GetChildren() do
		call_on_descendants(child, func)
	end
end

local function get_nearest_ancestor(instance, class_name)
	-- Returns the nearest ancestor of a certain instance which is of a certain type.
	instance = verify_arg(instance, 'Instance', "instance")
	class_name = verify_arg(class_name, 'string', "class_name")
	local ancestor = instance
	repeat
		ancestor = ancestor.Parent
		if ancestor == nil then
			return nil
		end
	until ancestor:IsA(class_name)
	return ancestor
end

local function get_character(descendant)
	-- Returns a character from one of its descendants.
	descendant = verify_arg(descendant, 'Instance', "descendant")
	local character = descendant
	repeat
		if character.Parent then
			character = character.Parent
		else
			return nil
		end
	until Players:GetPlayerFromCharacter(character)
	return character
end

local function show_message(text, lifetime)
	-- Shows a message for a certain time, which is set to be 3 seconds by default.
	text = verify_arg(text, 'string', "text")
	lifetime = verify_arg(lifetime, 'number', "lifetime")
	local message = Instance.new('Message')
	message.Text = text
	Debris:AddItem(message, lifetime or 3)
end

local function show_hint(text, lifetime)
	-- Shows a hint for a certain time, which is set to be 3 seconds by default.
	text = verify_arg(text, 'string', "text")
	lifetime = verify_arg(lifetime, 'number', "lifetime")
	local hint = Instance.new('Hint')
	hint.Text = text
	Debris:AddItem(hint, lifetime or 3)
end