508 lines
15 KiB
Lua
508 lines
15 KiB
Lua
local module = {}
|
|
|
|
-- PARTS TABLE
|
|
local parts = {}
|
|
_G.Storage:WaitForChild("Parts")
|
|
local function added(object)
|
|
if object.Parent:IsA("Folder") then
|
|
local id = object:GetAttribute("PartId")
|
|
if id then parts[id] = object end
|
|
end
|
|
end
|
|
for _,object in pairs(_G.Storage.Parts:GetDescendants()) do
|
|
added(object)
|
|
end
|
|
_G.Storage.Parts.DescendantAdded:Connect(function(object)
|
|
if object:GetAttribute("PartId") then
|
|
warn(string.format("%i:%s, you are late!",object:GetAttribute("PartId"),object:GetFullName()))
|
|
end
|
|
added(object)
|
|
end)
|
|
function module.get_template_part(part)
|
|
local id = part:GetAttribute("PartId")
|
|
if not id then return end
|
|
local template = parts[id]
|
|
return template
|
|
end
|
|
function module.get_part_by_id(id)
|
|
return parts[id]
|
|
end
|
|
|
|
function module.change_material(part,material)
|
|
if part.Material == Enum.Material.Glass then
|
|
part.Transparency = part:GetAttribute("OriginalTransparency",part.Transparency)
|
|
end
|
|
if material == Enum.Material.Glass then
|
|
part:SetAttribute("OriginalTransparency",part.Transparency)
|
|
part.Transparency = 0.5
|
|
end
|
|
part.Material = material
|
|
end
|
|
|
|
-- PART METHODS
|
|
function module.apply_material(part,material)
|
|
if material then
|
|
part:SetAttribute("Material",material.Name)
|
|
if part:IsA("Model") then
|
|
for _,subpart in pairs(part:GetDescendants()) do
|
|
if subpart:IsA("BasePart") then
|
|
module.change_material(subpart,material)
|
|
end
|
|
end
|
|
else
|
|
module.change_material(part,material)
|
|
end
|
|
end
|
|
end
|
|
function module.apply_edit(part,index,value)
|
|
part[index].Value = value
|
|
end
|
|
function module.get_size(part)
|
|
local nodes = module.get_nodes_class(part)
|
|
if nodes == 1 then
|
|
return part:GetAttribute("Size")
|
|
elseif nodes == 2 then
|
|
return Vector3.new(part.Size.Z,0,0) -- Yeah the bloody irony I know right?
|
|
elseif nodes >= 3 then
|
|
return Vector3.new(part:FindFirstChildWhichIsA("Part").Size.X,0,0)
|
|
end
|
|
end
|
|
function module.get_thickness(part)
|
|
return module.get_size(part).X -- WHAT?!
|
|
end
|
|
function module.apply_size(part,size)
|
|
part:SetAttribute("Size",size)
|
|
local nodes = module.get_nodes_class(part)
|
|
if nodes == 1 then
|
|
if part:IsA("Model") then
|
|
part:ScaleTo(size.X)
|
|
elseif part:IsA("BasePart") then
|
|
if size.Y == 0 or size.Z == 0 then return end -- Since this is not for normal parts it's a poly size
|
|
part.Size = size
|
|
end
|
|
elseif nodes == 2 then
|
|
part.Size = Vector3.new(part.Size.X,size.X,size.X) -- Yeah, this is serious
|
|
elseif nodes >= 3 then
|
|
for _,item in pairs(part:GetChildren()) do
|
|
item.Size = Vector3.new(size.X,item.Size.Y,item.Size.Z)
|
|
end
|
|
end
|
|
end
|
|
function module.apply_color(part,color)
|
|
part:SetAttribute("Color",color)
|
|
if part:IsA("Model") then
|
|
for _,subpart in pairs(part:GetDescendants()) do
|
|
if subpart:IsA("BasePart") then
|
|
subpart.Color = color
|
|
end
|
|
end
|
|
else
|
|
part.Color = color
|
|
end
|
|
end
|
|
local function Weld(part1,part2,temporary) -- Make a BUILDING weld
|
|
local weld = Instance.new("WeldConstraint")
|
|
weld.Name = "BuiltWeldConstraint"
|
|
weld.Parent = part1
|
|
weld.Part0 = part1
|
|
weld.Part1 = part2
|
|
if temporary then weld.Name = "WeldConstraint" end -- Or not.
|
|
return weld
|
|
end
|
|
function module.weld(part,connection,temporary)
|
|
local attach = part
|
|
if part:IsA("Model") then attach = attach.PrimaryPart end
|
|
local new_weld = Weld(attach,connection,temporary)
|
|
new_weld.Parent = part
|
|
return new_weld
|
|
end
|
|
function module.form_triangle(pa,pb,p1,p2,p3,s2)
|
|
local s1 = pa.Size.X
|
|
s2 = s2 or s1*0.5
|
|
s2 = 0
|
|
local p12 = p2-p1
|
|
local p23 = p3-p2
|
|
local p13 = p3-p1
|
|
local p12l = p12.Magnitude
|
|
local p23l = p23.Magnitude
|
|
local p13l = p13.Magnitude
|
|
local bottom
|
|
local bot_len
|
|
local totop
|
|
local root
|
|
local tip
|
|
local far
|
|
local t
|
|
if p12l > p23l and p12l > p13l then -- TRUE: p12 largest FALSE: p12 smaller than p23l or p13l
|
|
tip = p3
|
|
root = p1
|
|
far = p2
|
|
bottom = p12
|
|
bot_len = p12l
|
|
totop = p13
|
|
--[[pa.Color = Color3.new(1,0,0)
|
|
pb.Color = Color3.new(1,0,0)]]
|
|
elseif p23l > p13l then -- TRUE: p23 must be largest FALSE: p13 largest
|
|
tip = p1
|
|
root = p2
|
|
far = p3
|
|
bottom = p23
|
|
bot_len = p23l
|
|
totop = -p12
|
|
--[[pa.Color = Color3.new(0,1,0)
|
|
pb.Color = Color3.new(0,1,0)]]
|
|
else -- p13 largest
|
|
tip = p2
|
|
root = p1
|
|
far = p3
|
|
bottom = p13
|
|
bot_len = p13l
|
|
t = pa
|
|
pa = pb
|
|
pb = t
|
|
--[[pa.Color = Color3.new(0,0,1)
|
|
pb.Color = Color3.new(0,0,1)]]
|
|
totop = p12
|
|
end
|
|
bottom = bottom.Unit
|
|
local coeff = totop:Dot(bottom)
|
|
local mid = root+coeff*bottom
|
|
local cross = totop:Cross(bottom).Unit
|
|
local top = (mid-tip)
|
|
pa.Size = Vector3.new(s1,top.Magnitude,coeff)
|
|
pb.Size = Vector3.new(s1,top.Magnitude,bot_len - coeff)
|
|
top = top.Unit
|
|
pa.CFrame = CFrame.fromMatrix(s2*cross+(root+tip)*0.5,cross,-top,bottom)
|
|
pb.CFrame = CFrame.fromMatrix(s2*cross+(tip+far)*0.5,-cross,-top,-bottom)
|
|
end
|
|
function module.get_tri_positions1(pa,pb)
|
|
local ca = pa.CFrame
|
|
local ra = ca.Rotation
|
|
local cb = pb.CFrame.Position
|
|
local sa = pa.Size
|
|
local lb = pb.Size.Z
|
|
local lh = sa.Y
|
|
local fb = Vector3.new(0,lh,-lb)
|
|
return (cb - ra * (fb * 0.5)) -- FOR THIRD RETURN VALUE OF BELOW
|
|
end
|
|
function module.get_tri_positions2(pa,pb)
|
|
local ca = pa.CFrame
|
|
local ra = ca.Rotation
|
|
ca = ca.Position
|
|
local sa = pa.Size
|
|
local la = sa.Z
|
|
local lh = sa.Y
|
|
local fa = Vector3.new(0,lh,la)
|
|
return (ca + ra * (fa * -0.5)),(ca + ra * (fa * 0.5))
|
|
end
|
|
-- apply data positions of parts
|
|
local form_triangle = module.form_triangle
|
|
local number_to_string = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"}
|
|
local string_to_number = {}
|
|
for num,str in pairs(number_to_string) do
|
|
string_to_number[str] = num
|
|
end
|
|
--; for i = 1,10 do number_to_string[i] = tostring(i) end
|
|
local mod = CFrame.Angles(0,0,0)
|
|
function module.apply_nodes(part,nodes)
|
|
if #nodes < module.get_nodes_class(part) then return true end
|
|
if #nodes == 1 then
|
|
part:PivotTo(nodes[1])
|
|
elseif #nodes == 2 then
|
|
part.CFrame = nodes[1]*mod
|
|
part.Size = Vector3.new(nodes[2],part.Size.Y,part.Size.Z)
|
|
local first = part:FindFirstChild("1")
|
|
local second = part:FindFirstChild("2")
|
|
local p1,p2 = module.get_stick_ends(part)
|
|
local offset = Vector3.new(nodes[2]*0.5,0,0)
|
|
if first then
|
|
first.Position = nodes[1]:PointToWorldSpace(offset)
|
|
if first:FindFirstChild("WeldConstraint") then
|
|
first.WeldConstraint.Enabled = true
|
|
end
|
|
end
|
|
if second then
|
|
second.Position = nodes[1]:PointToWorldSpace(-offset)
|
|
if second:FindFirstChild("WeldConstraint") then
|
|
second.WeldConstraint.Enabled = true
|
|
end
|
|
end
|
|
elseif #nodes >= 3 then
|
|
local template = part["A"]
|
|
for i=1,(#nodes-2)*2 do
|
|
local name = number_to_string[i]
|
|
if not part:FindFirstChild(name) then
|
|
local piece = template:Clone()
|
|
piece.Parent = part
|
|
piece.Name = name
|
|
end
|
|
end
|
|
for i=1,(#nodes-2) do
|
|
form_triangle(part[number_to_string[i*2-1]],part[number_to_string[i*2]],nodes[1],nodes[i+1],nodes[i+2])
|
|
end
|
|
end
|
|
end
|
|
-- get the poly 'type' of a part
|
|
function module.get_nodes_class(part)
|
|
return part:GetAttribute("Nodes") or 1
|
|
end
|
|
-- get data positions of parts
|
|
function module.get_nodes(part)
|
|
local nodes = part:GetAttribute("Nodes")
|
|
if not nodes then
|
|
return {part:GetPivot()}
|
|
elseif nodes == 2 then
|
|
local first = part:FindFirstChild("1")
|
|
local second = part:FindFirstChild("2")
|
|
if first and second then
|
|
local offset = second.Position - first.Position
|
|
return {CFrame.lookAt(first.Position,second.Position,first.CFrame.UpVector)*CFrame.Angles(0,math.pi*0.5,0) + offset * 0.5,offset.Magnitude}
|
|
else
|
|
return {part.CFrame,part.Size.X}
|
|
end
|
|
elseif nodes >= 3 then
|
|
local i = -1
|
|
local p1 = part["A"]
|
|
local p2 = part["B"]
|
|
local t = {module.get_tri_positions2(p1,p2)}
|
|
while true do
|
|
i = i + 2
|
|
local pn1 = part:FindFirstChild(number_to_string[i])
|
|
if not pn1 then return t end
|
|
local pn2 = part:FindFirstChild(number_to_string[i+1])
|
|
if not pn2 then return t end
|
|
table.insert(t,module.get_tri_positions1(p1,p2))
|
|
end
|
|
return t
|
|
end
|
|
end
|
|
-- shift a poly part along its normal axis while keeping static node positions
|
|
function module.apply_shift(part,shift)
|
|
if module.get_nodes_class(part) < 3 then return end
|
|
local previous = part:GetAttribute("Shift") or 0
|
|
if shift == 0 and previous == 0 then part:SetAttribute("Shift",nil) return end
|
|
part:SetAttribute("Shift",shift)
|
|
shift = shift - previous
|
|
for _,item in pairs(part:GetChildren()) do
|
|
if item:IsA("BasePart") and not item:HasTag("Node") then
|
|
local neg = (string_to_number[item.Name] % 2 == 0)
|
|
if neg then neg = -1 else neg = 1 end
|
|
item.Position = item.Position + item.CFrame.RightVector * item.Size.X * 0.5 * shift * neg
|
|
end
|
|
end
|
|
end
|
|
-- stupid name, it just tries to shift in the direction based on how we're looking
|
|
function module.attempt_shift(part,shift,look)
|
|
local template = part:FindFirstChild("A")
|
|
if not template then return end
|
|
module.apply_shift(part,math.sign(template.CFrame.RightVector:Dot(look)) * shift)
|
|
end
|
|
-- get the cframe of a single connection
|
|
function module.eval_connection(connection)
|
|
local connected = connection.connected
|
|
local relative = CFrame.identity
|
|
if connected then relative = connected:GetPivot() end
|
|
return relative:ToWorldSpace(connection.CFrame)
|
|
end
|
|
function module.get_stick_ends(part)
|
|
local nodes = module.get_nodes(part)
|
|
if #nodes ~= 2 then error("Part is not a stick",2) end
|
|
local cframe = nodes[1]
|
|
local offset = Vector3.new(nodes[2]*0.5,0,0)
|
|
return cframe:PointToWorldSpace(offset),cframe:PointToWorldSpace(-offset)
|
|
end
|
|
function module.get_points(part)
|
|
local class = module.get_nodes_class(part)
|
|
if class == 1 then return {}
|
|
elseif class == 2 then return {module.get_stick_ends(part)}
|
|
else return module.get_nodes(part) end
|
|
end
|
|
-- apply driven connections by attachments in the placement engine
|
|
function module.eval_connections(part,connections)
|
|
local class = module.get_nodes_class(part)
|
|
local cframes = {}
|
|
for _,connection in ipairs(connections) do
|
|
table.insert(cframes,module.eval_connection(connection))
|
|
end
|
|
local nodes = {}
|
|
if class == 1 then
|
|
nodes = cframes
|
|
elseif class == 2 then
|
|
if #cframes == 2 then
|
|
local to = cframes[2].Position - cframes[1].Position
|
|
nodes = {CFrame.lookAt(cframes[1].Position,cframes[2].Position,cframes[1].UpVector)*CFrame.Angles(0,math.pi*0.5,0) + (to * 0.5), to.Magnitude}
|
|
end
|
|
elseif class == 3 then
|
|
for _,cframe in ipairs(cframes) do table.insert(nodes,cframe.Position) end
|
|
end
|
|
return nodes
|
|
end
|
|
|
|
function module.apply_internal_welds(part)
|
|
if part:IsA("Model") then
|
|
local main = part.PrimaryPart
|
|
for _,item in pairs(part:GetChildren()) do
|
|
if item:IsA("BasePart") and item ~= main then Weld(item,main,true) end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- PART STATE METHODS
|
|
local _state = {}
|
|
_state.__index = _state
|
|
function _state.apply_material(this)
|
|
if this.material then module.apply_material(this.part,this.material) end
|
|
end
|
|
function _state.apply_size(this)
|
|
if this.size then module.apply_size(this.part,this.size) end
|
|
end
|
|
function _state.apply_color(this)
|
|
if this.color then module.apply_color(this.part,this.color) end
|
|
end
|
|
function _state.apply_edit(this,index,value)
|
|
local edit = this.part:FindFirstChild(index)
|
|
if not edit then error("Could not find edit "..index,2) end
|
|
module.apply_edit(this.part,index,value)
|
|
end
|
|
function _state.apply_edits(this)
|
|
for index,value in pairs(this.edits) do
|
|
this:apply_edit(index,value)
|
|
end
|
|
end
|
|
function _state.attempt_shift(this,...)
|
|
module.attempt_shift(this.part,...)
|
|
end
|
|
function _state.apply_internal_welds(this)
|
|
if module.get_nodes_class(this.part) > 1 then -- Weld together parts inside of poly
|
|
module.apply_internal_welds(this.part)
|
|
end
|
|
end
|
|
function _state.apply_welds(this)
|
|
this:apply_internal_welds()
|
|
for index,connection in pairs(this.welds) do
|
|
local target = this.part
|
|
if connection.name then target = this.part[connection.name] end
|
|
if target:IsA("Model") then target = target.PrimaryPart end
|
|
if (not this.owner) or (_G.Methods.getOwnedOrNil(this.owner,connection.part)) then
|
|
module.weld(target,connection.part)
|
|
end
|
|
end
|
|
end
|
|
function _state.apply_connections(this)
|
|
this.nodes = module.eval_connections(this.part,this.connections)
|
|
end
|
|
function _state.apply_nodes(this)
|
|
module.apply_nodes(this.part,this.nodes)
|
|
end
|
|
function _state.apply_shift(this)
|
|
if this.look then this:attempt_shift(this.shift or 0,this.look)
|
|
else module.apply_shift(this.part,this.shift or 0) end
|
|
end
|
|
function _state.apply(this,part,options)
|
|
options = options or {}
|
|
this.part = part or this.part
|
|
if this.color then this:apply_color() end
|
|
if this.size then this:apply_size() end
|
|
if this.material then this:apply_material() end
|
|
if this.edits and not options.no_edit then this:apply_edits() end
|
|
if this.connections then this:apply_connections() end
|
|
if this.nodes then this:apply_nodes() end
|
|
if this.welds and not options.no_weld then this:apply_welds() else this:apply_internal_welds() end
|
|
if this.shift then this:apply_shift() end
|
|
end
|
|
function _state.create(this)
|
|
if not this.id then error("No id",2) end
|
|
local template = parts[this.id]:Clone()
|
|
template.Parent = game.Workspace.Objects
|
|
this:apply(template)
|
|
return template
|
|
end
|
|
function _state.get_nodes_class(this)
|
|
return module.get_nodes_class(this.part)
|
|
end
|
|
function _state.take(this,existing)
|
|
this.color = existing:GetAttribute("Color")
|
|
this.material = existing:GetAttribute("Material")
|
|
this.size = existing:GetAttribute("Size")
|
|
this.shift = existing:GetAttribute("Shift")
|
|
this.edits = {}
|
|
for _,edit in existing:GetChildren() do
|
|
if edit:HasTag("Edit") then
|
|
if edit.Value ~= (edit:GetAttribute("Default") or nil) then
|
|
this.edits[edit.Name] = edit.Value
|
|
end
|
|
end
|
|
end
|
|
this.nodes = module.get_nodes(existing)
|
|
return this
|
|
end
|
|
function _state.update(this)
|
|
this:take(this.part)
|
|
return this
|
|
end
|
|
function _state.copy(this,existing)
|
|
this:take(existing)
|
|
this.id = existing:GetAttribute("PartId")
|
|
this.part = parts[this.id]:Clone()
|
|
this:apply(nil,{no_weld = true})
|
|
return this
|
|
end
|
|
function module.copy(existing)
|
|
return module.new():copy(existing)
|
|
end
|
|
function module.new(original)
|
|
return setmetatable(_G.Union({
|
|
material = nil, -- Enum.Material SERIAL
|
|
color = nil, -- Color3.new() SERIAL
|
|
size = nil, -- Vector3.new() SERIAL
|
|
shift = nil, -- 1 -> 0 -> -1 SERIAL
|
|
connections = {}, -- {[n]={cframe=,connection=}} for placement engine etc
|
|
welds = {}, -- {{name=,touched=},...},... SERIAL
|
|
edits = {}, -- ? SERIAL
|
|
owner = nil, -- player
|
|
part = nil, -- part being worked with (applied)
|
|
id = nil -- part id SERIAL
|
|
},original or {}),_state)
|
|
end
|
|
|
|
-- SERIALISATION
|
|
--[[local function serialisation_factory(match_string)
|
|
local serialisation_table = {}
|
|
for index in match_string:gmatch("[^,]+") do
|
|
table.insert(serialisation_table,index)
|
|
end
|
|
return {
|
|
serialise = function(this)
|
|
local t = {}
|
|
for _,key in pairs(serialisation_table) do
|
|
table.insert(t,this[key])
|
|
end
|
|
return table.unpack(t)
|
|
end,
|
|
deserialise = function(this,...)
|
|
local t = {...}
|
|
for index,key in pairs(serialisation_table) do
|
|
this[key] = t[index]
|
|
end
|
|
end,
|
|
}
|
|
end
|
|
local state_serialiser = serialisation_factory("id,connections,color,size,shift,material,edits,welds")]]
|
|
_state.serialise = function(this) --state_serialiser.serialise
|
|
local color
|
|
if this.color then color = {this.color.R,this.color.G,this.color.B} end
|
|
return this.id,this.connections,color,this.size,this.shift,this.material,this.edits,this.welds,this.look
|
|
end
|
|
_state.deserialise = function(this,id,connections,color,size,shift,material,edits,welds,look) --state_serialiser.deserialise
|
|
if color then this.color = Color3.new(table.unpack(color)) end
|
|
this.id = id
|
|
this.connections = connections
|
|
this.size = size
|
|
this.shift = shift
|
|
this.material = material
|
|
this.edits = edits
|
|
this.welds = welds
|
|
this.look = look
|
|
end
|
|
|
|
return module |