forked from aya/aya
1118 lines
39 KiB
Lua
1118 lines
39 KiB
Lua
local t = {}
|
|
|
|
|
|
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------JSON Functions Begin----------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--JSON Encoder and Parser for Lua 5.1
|
|
--
|
|
--Copyright 2007 Shaun Brown (http://www.chipmunkav.com)
|
|
--All Rights Reserved.
|
|
|
|
--Permission is hereby granted, free of charge, to any person
|
|
--obtaining a copy of this software to deal in the Software without
|
|
--restriction, including without limitation the rights to use,
|
|
--copy, modify, merge, publish, distribute, sublicense, and/or
|
|
--sell copies of the Software, and to permit persons to whom the
|
|
--Software is furnished to do so, subject to the following conditions:
|
|
|
|
--The above copyright notice and this permission notice shall be
|
|
--included in all copies or substantial portions of the Software.
|
|
--If you find this software useful please give www.chipmunkav.com a mention.
|
|
|
|
--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
--EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
--OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
--IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
|
--ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
--CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
--CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
local string = string
|
|
local math = math
|
|
local table = table
|
|
local error = error
|
|
local tonumber = tonumber
|
|
local tostring = tostring
|
|
local type = type
|
|
local setmetatable = setmetatable
|
|
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local assert = assert
|
|
local Chipmunk = Chipmunk
|
|
|
|
|
|
local StringBuilder = {
|
|
buffer = {}
|
|
}
|
|
|
|
function StringBuilder:New()
|
|
local o = {}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
o.buffer = {}
|
|
return o
|
|
end
|
|
|
|
function StringBuilder:Append(s)
|
|
self.buffer[#self.buffer+1] = s
|
|
end
|
|
|
|
function StringBuilder:ToString()
|
|
return table.concat(self.buffer)
|
|
end
|
|
|
|
local JsonWriter = {
|
|
backslashes = {
|
|
['\b'] = "\\b",
|
|
['\t'] = "\\t",
|
|
['\n'] = "\\n",
|
|
['\f'] = "\\f",
|
|
['\r'] = "\\r",
|
|
['"'] = "\\\"",
|
|
['\\'] = "\\\\",
|
|
['/'] = "\\/"
|
|
}
|
|
}
|
|
|
|
function JsonWriter:New()
|
|
local o = {}
|
|
o.writer = StringBuilder:New()
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end
|
|
|
|
function JsonWriter:Append(s)
|
|
self.writer:Append(s)
|
|
end
|
|
|
|
function JsonWriter:ToString()
|
|
return self.writer:ToString()
|
|
end
|
|
|
|
function JsonWriter:Write(o)
|
|
local t = type(o)
|
|
if t == "nil" then
|
|
self:WriteNil()
|
|
elseif t == "boolean" then
|
|
self:WriteString(o)
|
|
elseif t == "number" then
|
|
self:WriteString(o)
|
|
elseif t == "string" then
|
|
self:ParseString(o)
|
|
elseif t == "table" then
|
|
self:WriteTable(o)
|
|
elseif t == "function" then
|
|
self:WriteFunction(o)
|
|
elseif t == "thread" then
|
|
self:WriteError(o)
|
|
elseif t == "userdata" then
|
|
self:WriteError(o)
|
|
end
|
|
end
|
|
|
|
function JsonWriter:WriteNil()
|
|
self:Append("null")
|
|
end
|
|
|
|
function JsonWriter:WriteString(o)
|
|
self:Append(tostring(o))
|
|
end
|
|
|
|
function JsonWriter:ParseString(s)
|
|
self:Append('"')
|
|
self:Append(string.gsub(s, "[%z%c\\\"/]", function(n)
|
|
local c = self.backslashes[n]
|
|
if c then return c end
|
|
return string.format("\\u%.4X", string.byte(n))
|
|
end))
|
|
self:Append('"')
|
|
end
|
|
|
|
function JsonWriter:IsArray(t)
|
|
local count = 0
|
|
local isindex = function(k)
|
|
if type(k) == "number" and k > 0 then
|
|
if math.floor(k) == k then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
for k,v in pairs(t) do
|
|
if not isindex(k) then
|
|
return false, '{', '}'
|
|
else
|
|
count = math.max(count, k)
|
|
end
|
|
end
|
|
return true, '[', ']', count
|
|
end
|
|
|
|
function JsonWriter:WriteTable(t)
|
|
local ba, st, et, n = self:IsArray(t)
|
|
self:Append(st)
|
|
if ba then
|
|
for i = 1, n do
|
|
self:Write(t[i])
|
|
if i < n then
|
|
self:Append(',')
|
|
end
|
|
end
|
|
else
|
|
local first = true;
|
|
for k, v in pairs(t) do
|
|
if not first then
|
|
self:Append(',')
|
|
end
|
|
first = false;
|
|
self:ParseString(k)
|
|
self:Append(':')
|
|
self:Write(v)
|
|
end
|
|
end
|
|
self:Append(et)
|
|
end
|
|
|
|
function JsonWriter:WriteError(o)
|
|
error(string.format(
|
|
"Encoding of %s unsupported",
|
|
tostring(o)))
|
|
end
|
|
|
|
function JsonWriter:WriteFunction(o)
|
|
if o == Null then
|
|
self:WriteNil()
|
|
else
|
|
self:WriteError(o)
|
|
end
|
|
end
|
|
|
|
local StringReader = {
|
|
s = "",
|
|
i = 0
|
|
}
|
|
|
|
function StringReader:New(s)
|
|
local o = {}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
o.s = s or o.s
|
|
return o
|
|
end
|
|
|
|
function StringReader:Peek()
|
|
local i = self.i + 1
|
|
if i <= #self.s then
|
|
return string.sub(self.s, i, i)
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function StringReader:Next()
|
|
self.i = self.i+1
|
|
if self.i <= #self.s then
|
|
return string.sub(self.s, self.i, self.i)
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function StringReader:All()
|
|
return self.s
|
|
end
|
|
|
|
local JsonReader = {
|
|
escapes = {
|
|
['t'] = '\t',
|
|
['n'] = '\n',
|
|
['f'] = '\f',
|
|
['r'] = '\r',
|
|
['b'] = '\b',
|
|
}
|
|
}
|
|
|
|
function JsonReader:New(s)
|
|
local o = {}
|
|
o.reader = StringReader:New(s)
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o;
|
|
end
|
|
|
|
function JsonReader:Read()
|
|
self:SkipWhiteSpace()
|
|
local peek = self:Peek()
|
|
if peek == nil then
|
|
error(string.format(
|
|
"Nil string: '%s'",
|
|
self:All()))
|
|
elseif peek == '{' then
|
|
return self:ReadObject()
|
|
elseif peek == '[' then
|
|
return self:ReadArray()
|
|
elseif peek == '"' then
|
|
return self:ReadString()
|
|
elseif string.find(peek, "[%+%-%d]") then
|
|
return self:ReadNumber()
|
|
elseif peek == 't' then
|
|
return self:ReadTrue()
|
|
elseif peek == 'f' then
|
|
return self:ReadFalse()
|
|
elseif peek == 'n' then
|
|
return self:ReadNull()
|
|
elseif peek == '/' then
|
|
self:ReadComment()
|
|
return self:Read()
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
function JsonReader:ReadTrue()
|
|
self:TestReservedWord{'t','r','u','e'}
|
|
return true
|
|
end
|
|
|
|
function JsonReader:ReadFalse()
|
|
self:TestReservedWord{'f','a','l','s','e'}
|
|
return false
|
|
end
|
|
|
|
function JsonReader:ReadNull()
|
|
self:TestReservedWord{'n','u','l','l'}
|
|
return nil
|
|
end
|
|
|
|
function JsonReader:TestReservedWord(t)
|
|
for i, v in ipairs(t) do
|
|
if self:Next() ~= v then
|
|
error(string.format(
|
|
"Error reading '%s': %s",
|
|
table.concat(t),
|
|
self:All()))
|
|
end
|
|
end
|
|
end
|
|
|
|
function JsonReader:ReadNumber()
|
|
local result = self:Next()
|
|
local peek = self:Peek()
|
|
while peek ~= nil and string.find(
|
|
peek,
|
|
"[%+%-%d%.eE]") do
|
|
result = result .. self:Next()
|
|
peek = self:Peek()
|
|
end
|
|
result = tonumber(result)
|
|
if result == nil then
|
|
error(string.format(
|
|
"Invalid number: '%s'",
|
|
result))
|
|
else
|
|
return result
|
|
end
|
|
end
|
|
|
|
function JsonReader:ReadString()
|
|
local result = ""
|
|
assert(self:Next() == '"')
|
|
while self:Peek() ~= '"' do
|
|
local ch = self:Next()
|
|
if ch == '\\' then
|
|
ch = self:Next()
|
|
if self.escapes[ch] then
|
|
ch = self.escapes[ch]
|
|
end
|
|
end
|
|
result = result .. ch
|
|
end
|
|
assert(self:Next() == '"')
|
|
local fromunicode = function(m)
|
|
return string.char(tonumber(m, 16))
|
|
end
|
|
return string.gsub(
|
|
result,
|
|
"u%x%x(%x%x)",
|
|
fromunicode)
|
|
end
|
|
|
|
function JsonReader:ReadComment()
|
|
assert(self:Next() == '/')
|
|
local second = self:Next()
|
|
if second == '/' then
|
|
self:ReadSingleLineComment()
|
|
elseif second == '*' then
|
|
self:ReadBlockComment()
|
|
else
|
|
error(string.format(
|
|
"Invalid comment: %s",
|
|
self:All()))
|
|
end
|
|
end
|
|
|
|
function JsonReader:ReadBlockComment()
|
|
local done = false
|
|
while not done do
|
|
local ch = self:Next()
|
|
if ch == '*' and self:Peek() == '/' then
|
|
done = true
|
|
end
|
|
if not done and
|
|
ch == '/' and
|
|
self:Peek() == "*" then
|
|
error(string.format(
|
|
"Invalid comment: %s, '/*' illegal.",
|
|
self:All()))
|
|
end
|
|
end
|
|
self:Next()
|
|
end
|
|
|
|
function JsonReader:ReadSingleLineComment()
|
|
local ch = self:Next()
|
|
while ch ~= '\r' and ch ~= '\n' do
|
|
ch = self:Next()
|
|
end
|
|
end
|
|
|
|
function JsonReader:ReadArray()
|
|
local result = {}
|
|
assert(self:Next() == '[')
|
|
local done = false
|
|
if self:Peek() == ']' then
|
|
done = true;
|
|
end
|
|
while not done do
|
|
local item = self:Read()
|
|
result[#result+1] = item
|
|
self:SkipWhiteSpace()
|
|
if self:Peek() == ']' then
|
|
done = true
|
|
end
|
|
if not done then
|
|
local ch = self:Next()
|
|
if ch ~= ',' then
|
|
error(string.format(
|
|
"Invalid array: '%s' due to: '%s'",
|
|
self:All(), ch))
|
|
end
|
|
end
|
|
end
|
|
assert(']' == self:Next())
|
|
return result
|
|
end
|
|
|
|
function JsonReader:ReadObject()
|
|
local result = {}
|
|
assert(self:Next() == '{')
|
|
local done = false
|
|
if self:Peek() == '}' then
|
|
done = true
|
|
end
|
|
while not done do
|
|
local key = self:Read()
|
|
if type(key) ~= "string" then
|
|
error(string.format(
|
|
"Invalid non-string object key: %s",
|
|
key))
|
|
end
|
|
self:SkipWhiteSpace()
|
|
local ch = self:Next()
|
|
if ch ~= ':' then
|
|
error(string.format(
|
|
"Invalid object: '%s' due to: '%s'",
|
|
self:All(),
|
|
ch))
|
|
end
|
|
self:SkipWhiteSpace()
|
|
local val = self:Read()
|
|
result[key] = val
|
|
self:SkipWhiteSpace()
|
|
if self:Peek() == '}' then
|
|
done = true
|
|
end
|
|
if not done then
|
|
ch = self:Next()
|
|
if ch ~= ',' then
|
|
error(string.format(
|
|
"Invalid array: '%s' near: '%s'",
|
|
self:All(),
|
|
ch))
|
|
end
|
|
end
|
|
end
|
|
assert(self:Next() == "}")
|
|
return result
|
|
end
|
|
|
|
function JsonReader:SkipWhiteSpace()
|
|
local p = self:Peek()
|
|
while p ~= nil and string.find(p, "[%s/]") do
|
|
if p == '/' then
|
|
self:ReadComment()
|
|
else
|
|
self:Next()
|
|
end
|
|
p = self:Peek()
|
|
end
|
|
end
|
|
|
|
function JsonReader:Peek()
|
|
return self.reader:Peek()
|
|
end
|
|
|
|
function JsonReader:Next()
|
|
return self.reader:Next()
|
|
end
|
|
|
|
function JsonReader:All()
|
|
return self.reader:All()
|
|
end
|
|
|
|
function Encode(o)
|
|
local writer = JsonWriter:New()
|
|
writer:Write(o)
|
|
return writer:ToString()
|
|
end
|
|
|
|
function Decode(s)
|
|
local reader = JsonReader:New(s)
|
|
return reader:Read()
|
|
end
|
|
|
|
function Null()
|
|
return Null
|
|
end
|
|
-------------------- End JSON Parser ------------------------
|
|
|
|
t.DecodeJSON = function(jsonString)
|
|
pcall(function() warn("RbxUtility.DecodeJSON is deprecated, please use Game:GetService('HttpService'):JSONDecode() instead.") end)
|
|
|
|
if type(jsonString) == "string" then
|
|
return Decode(jsonString)
|
|
end
|
|
print("RbxUtil.DecodeJSON expects string argument!")
|
|
return nil
|
|
end
|
|
|
|
t.EncodeJSON = function(jsonTable)
|
|
pcall(function() warn("RbxUtility.EncodeJSON is deprecated, please use Game:GetService('HttpService'):JSONEncode() instead.") end)
|
|
return Encode(jsonTable)
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
--------------------------------------------Terrain Utilities Begin-----------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
--makes a wedge at location x, y, z
|
|
--sets cell x, y, z to default material if parameter is provided, if not sets cell x, y, z to be whatever material it previously w
|
|
--returns true if made a wedge, false if the cell remains a block
|
|
t.MakeWedge = function(x, y, z, defaultmaterial)
|
|
return game:GetService("Terrain"):AutoWedgeCell(x,y,z)
|
|
end
|
|
|
|
t.SelectTerrainRegion = function(regionToSelect, color, selectEmptyCells, selectionParent)
|
|
local terrain = game:GetService("Workspace"):FindFirstChild("Terrain")
|
|
if not terrain then return end
|
|
|
|
assert(regionToSelect)
|
|
assert(color)
|
|
|
|
if not type(regionToSelect) == "Region3" then
|
|
error("regionToSelect (first arg), should be of type Region3, but is type",type(regionToSelect))
|
|
end
|
|
if not type(color) == "BrickColor" then
|
|
error("color (second arg), should be of type BrickColor, but is type",type(color))
|
|
end
|
|
|
|
-- frequently used terrain calls (speeds up call, no lookup necessary)
|
|
local GetCell = terrain.GetCell
|
|
local WorldToCellPreferSolid = terrain.WorldToCellPreferSolid
|
|
local CellCenterToWorld = terrain.CellCenterToWorld
|
|
local emptyMaterial = Enum.CellMaterial.Empty
|
|
|
|
-- container for all adornments, passed back to user
|
|
local selectionContainer = Instance.new("Model")
|
|
selectionContainer.Name = "SelectionContainer"
|
|
selectionContainer.Archivable = false
|
|
if selectionParent then
|
|
selectionContainer.Parent = selectionParent
|
|
else
|
|
selectionContainer.Parent = game:GetService("Workspace")
|
|
end
|
|
|
|
local updateSelection = nil -- function we return to allow user to update selection
|
|
local currentKeepAliveTag = nil -- a tag that determines whether adorns should be destroyed
|
|
local aliveCounter = 0 -- helper for currentKeepAliveTag
|
|
local lastRegion = nil -- used to stop updates that do nothing
|
|
local adornments = {} -- contains all adornments
|
|
local reusableAdorns = {}
|
|
|
|
local selectionPart = Instance.new("Part")
|
|
selectionPart.Name = "SelectionPart"
|
|
selectionPart.Transparency = 1
|
|
selectionPart.Anchored = true
|
|
selectionPart.Locked = true
|
|
selectionPart.CanCollide = false
|
|
selectionPart.FormFactor = Enum.FormFactor.Custom
|
|
selectionPart.Size = Vector3.new(4.2,4.2,4.2)
|
|
|
|
local selectionBox = Instance.new("SelectionBox")
|
|
|
|
-- srs translation from region3 to region3int16
|
|
function Region3ToRegion3int16(region3)
|
|
local theLowVec = region3.CFrame.p - (region3.Size/2) + Vector3.new(2,2,2)
|
|
local lowCell = WorldToCellPreferSolid(terrain,theLowVec)
|
|
|
|
local theHighVec = region3.CFrame.p + (region3.Size/2) - Vector3.new(2,2,2)
|
|
local highCell = WorldToCellPreferSolid(terrain, theHighVec)
|
|
|
|
local highIntVec = Vector3int16.new(highCell.x,highCell.y,highCell.z)
|
|
local lowIntVec = Vector3int16.new(lowCell.x,lowCell.y,lowCell.z)
|
|
|
|
return Region3int16.new(lowIntVec,highIntVec)
|
|
end
|
|
|
|
-- helper function that creates the basis for a selection box
|
|
function createAdornment(theColor)
|
|
local selectionPartClone = nil
|
|
local selectionBoxClone = nil
|
|
|
|
if #reusableAdorns > 0 then
|
|
selectionPartClone = reusableAdorns[1]["part"]
|
|
selectionBoxClone = reusableAdorns[1]["box"]
|
|
table.remove(reusableAdorns,1)
|
|
|
|
selectionBoxClone.Visible = true
|
|
else
|
|
selectionPartClone = selectionPart:Clone()
|
|
selectionPartClone.Archivable = false
|
|
|
|
selectionBoxClone = selectionBox:Clone()
|
|
selectionBoxClone.Archivable = false
|
|
|
|
selectionBoxClone.Adornee = selectionPartClone
|
|
selectionBoxClone.Parent = selectionContainer
|
|
|
|
selectionBoxClone.Adornee = selectionPartClone
|
|
|
|
selectionBoxClone.Parent = selectionContainer
|
|
end
|
|
|
|
if theColor then
|
|
selectionBoxClone.Color = theColor
|
|
end
|
|
|
|
return selectionPartClone, selectionBoxClone
|
|
end
|
|
|
|
-- iterates through all current adornments and deletes any that don't have latest tag
|
|
function cleanUpAdornments()
|
|
for cellPos, adornTable in pairs(adornments) do
|
|
|
|
if adornTable.KeepAlive ~= currentKeepAliveTag then -- old news, we should get rid of this
|
|
adornTable.SelectionBox.Visible = false
|
|
table.insert(reusableAdorns,{part = adornTable.SelectionPart, box = adornTable.SelectionBox})
|
|
adornments[cellPos] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- helper function to update tag
|
|
function incrementAliveCounter()
|
|
aliveCounter = aliveCounter + 1
|
|
if aliveCounter > 1000000 then
|
|
aliveCounter = 0
|
|
end
|
|
return aliveCounter
|
|
end
|
|
|
|
-- finds full cells in region and adorns each cell with a box, with the argument color
|
|
function adornFullCellsInRegion(region, color)
|
|
local regionBegin = region.CFrame.p - (region.Size/2) + Vector3.new(2,2,2)
|
|
local regionEnd = region.CFrame.p + (region.Size/2) - Vector3.new(2,2,2)
|
|
|
|
local cellPosBegin = WorldToCellPreferSolid(terrain, regionBegin)
|
|
local cellPosEnd = WorldToCellPreferSolid(terrain, regionEnd)
|
|
|
|
currentKeepAliveTag = incrementAliveCounter()
|
|
for y = cellPosBegin.y, cellPosEnd.y do
|
|
for z = cellPosBegin.z, cellPosEnd.z do
|
|
for x = cellPosBegin.x, cellPosEnd.x do
|
|
local cellMaterial = GetCell(terrain, x, y, z)
|
|
|
|
if cellMaterial ~= emptyMaterial then
|
|
local cframePos = CellCenterToWorld(terrain, x, y, z)
|
|
local cellPos = Vector3int16.new(x,y,z)
|
|
|
|
local updated = false
|
|
for cellPosAdorn, adornTable in pairs(adornments) do
|
|
if cellPosAdorn == cellPos then
|
|
adornTable.KeepAlive = currentKeepAliveTag
|
|
if color then
|
|
adornTable.SelectionBox.Color = color
|
|
end
|
|
updated = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if not updated then
|
|
local selectionPart, selectionBox = createAdornment(color)
|
|
selectionPart.Size = Vector3.new(4,4,4)
|
|
selectionPart.CFrame = CFrame.new(cframePos)
|
|
local adornTable = {SelectionPart = selectionPart, SelectionBox = selectionBox, KeepAlive = currentKeepAliveTag}
|
|
adornments[cellPos] = adornTable
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
cleanUpAdornments()
|
|
end
|
|
|
|
|
|
------------------------------------- setup code ------------------------------
|
|
lastRegion = regionToSelect
|
|
|
|
if selectEmptyCells then -- use one big selection to represent the area selected
|
|
local selectionPart, selectionBox = createAdornment(color)
|
|
|
|
selectionPart.Size = regionToSelect.Size
|
|
selectionPart.CFrame = regionToSelect.CFrame
|
|
|
|
adornments.SelectionPart = selectionPart
|
|
adornments.SelectionBox = selectionBox
|
|
|
|
updateSelection =
|
|
function (newRegion, color)
|
|
if newRegion and newRegion ~= lastRegion then
|
|
lastRegion = newRegion
|
|
selectionPart.Size = newRegion.Size
|
|
selectionPart.CFrame = newRegion.CFrame
|
|
end
|
|
if color then
|
|
selectionBox.Color = color
|
|
end
|
|
end
|
|
else -- use individual cell adorns to represent the area selected
|
|
adornFullCellsInRegion(regionToSelect, color)
|
|
updateSelection =
|
|
function (newRegion, color)
|
|
if newRegion and newRegion ~= lastRegion then
|
|
lastRegion = newRegion
|
|
adornFullCellsInRegion(newRegion, color)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
local destroyFunc = function()
|
|
updateSelection = nil
|
|
if selectionContainer then selectionContainer:Destroy() end
|
|
adornments = nil
|
|
end
|
|
|
|
return updateSelection, destroyFunc
|
|
end
|
|
|
|
-----------------------------Terrain Utilities End-----------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------Signal class begin------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
--[[
|
|
A 'Signal' object identical to the internal RBXScriptSignal object in it's public API and semantics. This function
|
|
can be used to create "custom events" for user-made code.
|
|
API:
|
|
Method :connect( function handler )
|
|
Arguments: The function to connect to.
|
|
Returns: A new connection object which can be used to disconnect the connection
|
|
Description: Connects this signal to the function specified by |handler|. That is, when |fire( ... )| is called for
|
|
the signal the |handler| will be called with the arguments given to |fire( ... )|. Note, the functions
|
|
connected to a signal are called in NO PARTICULAR ORDER, so connecting one function after another does
|
|
NOT mean that the first will be called before the second as a result of a call to |fire|.
|
|
|
|
Method :disconnect()
|
|
Arguments: None
|
|
Returns: None
|
|
Description: Disconnects all of the functions connected to this signal.
|
|
|
|
Method :fire( ... )
|
|
Arguments: Any arguments are accepted
|
|
Returns: None
|
|
Description: Calls all of the currently connected functions with the given arguments.
|
|
|
|
Method :wait()
|
|
Arguments: None
|
|
Returns: The arguments given to fire
|
|
Description: This call blocks until
|
|
]]
|
|
|
|
function t.CreateSignal()
|
|
local this = {}
|
|
|
|
local mBindableEvent = Instance.new('BindableEvent')
|
|
local mAllCns = {} --all connection objects returned by mBindableEvent::connect
|
|
|
|
--main functions
|
|
function this:connect(func)
|
|
if self ~= this then error("connect must be called with `:`, not `.`", 2) end
|
|
if type(func) ~= 'function' then
|
|
error("Argument #1 of connect must be a function, got a "..type(func), 2)
|
|
end
|
|
local cn = mBindableEvent.Event:connect(func)
|
|
mAllCns[cn] = true
|
|
local pubCn = {}
|
|
function pubCn:disconnect()
|
|
cn:disconnect()
|
|
mAllCns[cn] = nil
|
|
end
|
|
return pubCn
|
|
end
|
|
function this:disconnect()
|
|
if self ~= this then error("disconnect must be called with `:`, not `.`", 2) end
|
|
for cn, _ in pairs(mAllCns) do
|
|
cn:disconnect()
|
|
mAllCns[cn] = nil
|
|
end
|
|
end
|
|
function this:wait()
|
|
if self ~= this then error("wait must be called with `:`, not `.`", 2) end
|
|
return mBindableEvent.Event:wait()
|
|
end
|
|
function this:fire(...)
|
|
if self ~= this then error("fire must be called with `:`, not `.`", 2) end
|
|
mBindableEvent:Fire(...)
|
|
end
|
|
|
|
return this
|
|
end
|
|
|
|
------------------------------------------------- Sigal class End ------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
-----------------------------------------------Create Function Begins---------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
--[[
|
|
A "Create" function for easy creation of Roblox instances. The function accepts a string which is the classname of
|
|
the object to be created. The function then returns another function which either accepts accepts no arguments, in
|
|
which case it simply creates an object of the given type, or a table argument that may contain several types of data,
|
|
in which case it mutates the object in varying ways depending on the nature of the aggregate data. These are the
|
|
type of data and what operation each will perform:
|
|
1) A string key mapping to some value:
|
|
Key-Value pairs in this form will be treated as properties of the object, and will be assigned in NO PARTICULAR
|
|
ORDER. If the order in which properties is assigned matter, then they must be assigned somewhere else than the
|
|
|Create| call's body.
|
|
|
|
2) An integral key mapping to another Instance:
|
|
Normal numeric keys mapping to Instances will be treated as children if the object being created, and will be
|
|
parented to it. This allows nice recursive calls to Create to create a whole hierarchy of objects without a
|
|
need for temporary variables to store references to those objects.
|
|
|
|
3) A key which is a value returned from Create.Event( eventname ), and a value which is a function function
|
|
The Create.E( string ) function provides a limited way to connect to signals inside of a Create hierarchy
|
|
for those who really want such a functionality. The name of the event whose name is passed to
|
|
Create.E( string )
|
|
|
|
4) A key which is the Create function itself, and a value which is a function
|
|
The function will be run with the argument of the object itself after all other initialization of the object is
|
|
done by create. This provides a way to do arbitrary things involving the object from withing the create
|
|
hierarchy.
|
|
Note: This function is called SYNCHRONOUSLY, that means that you should only so initialization in
|
|
it, not stuff which requires waiting, as the Create call will block until it returns. While waiting in the
|
|
constructor callback function is possible, it is probably not a good design choice.
|
|
Note: Since the constructor function is called after all other initialization, a Create block cannot have two
|
|
constructor functions, as it would not be possible to call both of them last, also, this would be unnecessary.
|
|
|
|
|
|
Some example usages:
|
|
|
|
A simple example which uses the Create function to create a model object and assign two of it's properties.
|
|
local model = Create'Model'{
|
|
Name = 'A New model',
|
|
Parent = game.Workspace,
|
|
}
|
|
|
|
|
|
An example where a larger hierarchy of object is made. After the call the hierarchy will look like this:
|
|
Model_Container
|
|
|-ObjectValue
|
|
| |
|
|
| `-BoolValueChild
|
|
`-IntValue
|
|
|
|
local model = Create'Model'{
|
|
Name = 'Model_Container',
|
|
Create'ObjectValue'{
|
|
Create'BoolValue'{
|
|
Name = 'BoolValueChild',
|
|
},
|
|
},
|
|
Create'IntValue'{},
|
|
}
|
|
|
|
|
|
An example using the event syntax:
|
|
|
|
local part = Create'Part'{
|
|
[Create.E'Touched'] = function(part)
|
|
print("I was touched by "..part.Name)
|
|
end,
|
|
}
|
|
|
|
|
|
An example using the general constructor syntax:
|
|
|
|
local model = Create'Part'{
|
|
[Create] = function(this)
|
|
print("Constructor running!")
|
|
this.Name = GetGlobalFoosAndBars(this)
|
|
end,
|
|
}
|
|
|
|
|
|
Note: It is also perfectly legal to save a reference to the function returned by a call Create, this will not cause
|
|
any unexpected behavior. EG:
|
|
local partCreatingFunction = Create'Part'
|
|
local part = partCreatingFunction()
|
|
]]
|
|
|
|
--the Create function need to be created as a functor, not a function, in order to support the Create.E syntax, so it
|
|
--will be created in several steps rather than as a single function declaration.
|
|
local function Create_PrivImpl(objectType)
|
|
if type(objectType) ~= 'string' then
|
|
error("Argument of Create must be a string", 2)
|
|
end
|
|
--return the proxy function that gives us the nice Create'string'{data} syntax
|
|
--The first function call is a function call using Lua's single-string-argument syntax
|
|
--The second function call is using Lua's single-table-argument syntax
|
|
--Both can be chained together for the nice effect.
|
|
return function(dat)
|
|
--default to nothing, to handle the no argument given case
|
|
dat = dat or {}
|
|
|
|
--make the object to mutate
|
|
local obj = Instance.new(objectType)
|
|
|
|
--stored constructor function to be called after other initialization
|
|
local ctor = nil
|
|
|
|
for k, v in pairs(dat) do
|
|
--add property
|
|
if type(k) == 'string' then
|
|
obj[k] = v
|
|
|
|
|
|
--add child
|
|
elseif type(k) == 'number' then
|
|
if type(v) ~= 'userdata' then
|
|
error("Bad entry in Create body: Numeric keys must be paired with children, got a: "..type(v), 2)
|
|
end
|
|
v.Parent = obj
|
|
|
|
|
|
--event connect
|
|
elseif type(k) == 'table' and k.__eventname then
|
|
if type(v) ~= 'function' then
|
|
error("Bad entry in Create body: Key `[Create.E\'"..k.__eventname.."\']` must have a function value\
|
|
got: "..tostring(v), 2)
|
|
end
|
|
obj[k.__eventname]:connect(v)
|
|
|
|
|
|
--define constructor function
|
|
elseif k == t.Create then
|
|
if type(v) ~= 'function' then
|
|
error("Bad entry in Create body: Key `[Create]` should be paired with a constructor function, \
|
|
got: "..tostring(v), 2)
|
|
elseif ctor then
|
|
--ctor already exists, only one allowed
|
|
error("Bad entry in Create body: Only one constructor function is allowed", 2)
|
|
end
|
|
ctor = v
|
|
|
|
|
|
else
|
|
error("Bad entry ("..tostring(k).." => "..tostring(v)..") in Create body", 2)
|
|
end
|
|
end
|
|
|
|
--apply constructor function if it exists
|
|
if ctor then
|
|
ctor(obj)
|
|
end
|
|
|
|
--return the completed object
|
|
return obj
|
|
end
|
|
end
|
|
|
|
--now, create the functor:
|
|
t.Create = setmetatable({}, {__call = function(tb, ...) return Create_PrivImpl(...) end})
|
|
|
|
--and create the "Event.E" syntax stub. Really it's just a stub to construct a table which our Create
|
|
--function can recognize as special.
|
|
t.Create.E = function(eventName)
|
|
return {__eventname = eventName}
|
|
end
|
|
|
|
-------------------------------------------------Create function End----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------Documentation Begin-----------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
------------------------------------------------------------------------------------------------------------------------
|
|
|
|
t.Help =
|
|
function(funcNameOrFunc)
|
|
--input argument can be a string or a function. Should return a description (of arguments and expected side effects)
|
|
if funcNameOrFunc == "DecodeJSON" or funcNameOrFunc == t.DecodeJSON then
|
|
return "Function DecodeJSON. " ..
|
|
"Arguments: (string). " ..
|
|
"Side effect: returns a table with all parsed JSON values"
|
|
end
|
|
if funcNameOrFunc == "EncodeJSON" or funcNameOrFunc == t.EncodeJSON then
|
|
return "Function EncodeJSON. " ..
|
|
"Arguments: (table). " ..
|
|
"Side effect: returns a string composed of argument table in JSON data format"
|
|
end
|
|
if funcNameOrFunc == "MakeWedge" or funcNameOrFunc == t.MakeWedge then
|
|
return "Function MakeWedge. " ..
|
|
"Arguments: (x, y, z, [default material]). " ..
|
|
"Description: Makes a wedge at location x, y, z. Sets cell x, y, z to default material if "..
|
|
"parameter is provided, if not sets cell x, y, z to be whatever material it previously was. "..
|
|
"Returns true if made a wedge, false if the cell remains a block "
|
|
end
|
|
if funcNameOrFunc == "SelectTerrainRegion" or funcNameOrFunc == t.SelectTerrainRegion then
|
|
return "Function SelectTerrainRegion. " ..
|
|
"Arguments: (regionToSelect, color, selectEmptyCells, selectionParent). " ..
|
|
"Description: Selects all terrain via a series of selection boxes within the regionToSelect " ..
|
|
"(this should be a region3 value). The selection box color is detemined by the color argument " ..
|
|
"(should be a brickcolor value). SelectionParent is the parent that the selection model gets placed to (optional)." ..
|
|
"SelectEmptyCells is bool, when true will select all cells in the " ..
|
|
"region, otherwise we only select non-empty cells. Returns a function that can update the selection," ..
|
|
"arguments to said function are a new region3 to select, and the adornment color (color arg is optional). " ..
|
|
"Also returns a second function that takes no arguments and destroys the selection"
|
|
end
|
|
if funcNameOrFunc == "CreateSignal" or funcNameOrFunc == t.CreateSignal then
|
|
return "Function CreateSignal. "..
|
|
"Arguments: None. "..
|
|
"Returns: The newly created Signal object. This object is identical to the RBXScriptSignal class "..
|
|
"used for events in Objects, but is a Lua-side object so it can be used to create custom events in"..
|
|
"Lua code. "..
|
|
"Methods of the Signal object: :connect, :wait, :fire, :disconnect. "..
|
|
"For more info you can pass the method name to the Help function, or view the wiki page "..
|
|
"for this library. EG: Help('Signal:connect')."
|
|
end
|
|
if funcNameOrFunc == "Signal:connect" then
|
|
return "Method Signal:connect. "..
|
|
"Arguments: (function handler). "..
|
|
"Return: A connection object which can be used to disconnect the connection to this handler. "..
|
|
"Description: Connectes a handler function to this Signal, so that when |fire| is called the "..
|
|
"handler function will be called with the arguments passed to |fire|."
|
|
end
|
|
if funcNameOrFunc == "Signal:wait" then
|
|
return "Method Signal:wait. "..
|
|
"Arguments: None. "..
|
|
"Returns: The arguments passed to the next call to |fire|. "..
|
|
"Description: This call does not return until the next call to |fire| is made, at which point it "..
|
|
"will return the values which were passed as arguments to that |fire| call."
|
|
end
|
|
if funcNameOrFunc == "Signal:fire" then
|
|
return "Method Signal:fire. "..
|
|
"Arguments: Any number of arguments of any type. "..
|
|
"Returns: None. "..
|
|
"Description: This call will invoke any connected handler functions, and notify any waiting code "..
|
|
"attached to this Signal to continue, with the arguments passed to this function. Note: The calls "..
|
|
"to handlers are made asynchronously, so this call will return immediately regardless of how long "..
|
|
"it takes the connected handler functions to complete."
|
|
end
|
|
if funcNameOrFunc == "Signal:disconnect" then
|
|
return "Method Signal:disconnect. "..
|
|
"Arguments: None. "..
|
|
"Returns: None. "..
|
|
"Description: This call disconnects all handlers attacched to this function, note however, it "..
|
|
"does NOT make waiting code continue, as is the behavior of normal Roblox events. This method "..
|
|
"can also be called on the connection object which is returned from Signal:connect to only "..
|
|
"disconnect a single handler, as opposed to this method, which will disconnect all handlers."
|
|
end
|
|
if funcNameOrFunc == "Create" then
|
|
return "Function Create. "..
|
|
"Arguments: A table containing information about how to construct a collection of objects. "..
|
|
"Returns: The constructed objects. "..
|
|
"Descrition: Create is a very powerfull function, whose description is too long to fit here, and "..
|
|
"is best described via example, please see the wiki page for a description of how to use it."
|
|
end
|
|
end
|
|
|
|
--------------------------------------------Documentation Ends----------------------------------------------------------
|
|
|
|
return t
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|