Files
PlaneBuilding/scripts/modules/PartStateModule.lua
T

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