--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] local string_format = string.format local tostring = tostring local pace = pace local assert = assert local debug_traceback = debug.traceback local math_random = math.random local xpcall = xpcall local pac = pac local pairs = pairs local ipairs = ipairs local table = table local Color = Color local NULL = NULL local table_insert = table.insert local BUILDER, PART = pac.PartTemplate() PART.ClassName = "base" PART.BaseName = PART.ClassName function PART:__tostring() return string_format("part[%s][%s][%i]", self.ClassName, self:GetName(), self.Id) end BUILDER :GetSet("PlayerOwner", NULL) :GetSet("Owner", NULL) :GetSet("Enabled", true) BUILDER :StartStorableVars() :SetPropertyGroup("generic") :GetSet("Name", "") :GetSet("Hide", false) :GetSet("EditorExpand", false, {hidden = true}) :GetSet("UniqueID", "", {hidden = true}) :GetSetPart("Parent") :GetSetPart("TargetEntity", {description = "allows you to change which entity this part targets"}) -- this is an unfortunate name, it controls the order in which the scene related functions iterate over children -- in practice it's often used to make something draw above something else in translucent rendering :GetSet("DrawOrder", 0) :GetSet("IsDisturbing", false, { editor_friendly = "IsExplicit", description = "Marks this content as NSFW, and makes it hidden for most of players who have pac_hide_disturbing set to 1" }) :EndStorableVars() PART.is_valid = true function PART:IsValid() return self.is_valid end function PART:PreInitialize() self.Children = {} self.ChildrenMap = {} self.modifiers = {} self.RootPart = NULL self.DrawOrder = 0 self.hide_disturbing = false self.active_events = {} self.active_events_ref_count = 0 local cvarName = "pac_enable_" .. string.Replace(self.ClassName, " ", "_"):lower() if not GetConVar(cvarName):GetBool() then self:SetWarning("This part class is disabled! Enable it with " .. cvarName .. " 1") end end function PART:Initialize() end function PART:OnRemove() end function PART:GetNiceName() return self.ClassName end function PART:GetPrintUniqueID() if not self.UniqueID then return '00000000' end return self.UniqueID:sub(1, 8) end function PART:GetName() if self.Name == "" then -- recursive call guard if self.last_nice_name_frame and self.last_nice_name_frame == pac.FrameNumber then return self.last_nice_name end self.last_nice_name_frame = pac.FrameNumber local nice = self:GetNiceName() local num local count = 0 if self:HasParent() then for _, val in ipairs(self:GetParent():GetChildren()) do if val:GetNiceName() == nice then count = count + 1 if val == self then num = count end end end end if num and count > 1 and num > 1 then nice = nice:Trim() .. " (" .. num - 1 .. ")" end self.last_nice_name = nice return nice end return self.Name end function PART:SetUniqueID(id) if id then local existing = pac.GetPartFromUniqueID(self:GetPlayerOwnerId(), id) if existing:IsValid() then pac.Message(Color(255, 50, 50), "unique id collision between ", self, " and ", existing) id = nil end end id = id or pac.Hash() local owner_id = self:GetPlayerOwnerId() if owner_id then pac.RemoveUniqueIDPart(owner_id, self.UniqueID) end self.UniqueID = id if owner_id then pac.SetUniqueIDPart(owner_id, id, self) end end local function set_info(msg, info_type) if not msg then return nil end local msg = tostring(msg) return { message = msg, type = info_type or 1 } end function PART:SetInfo(message) self.Info = set_info(message, 1) end function PART:SetWarning(message) self.Info = set_info(message, 2) end function PART:SetError(message) self.Info = set_info(message, 3) end do -- owner function PART:SetPlayerOwner(ply) local owner_id = self:GetPlayerOwnerId() self.PlayerOwner = ply if ply and ply:IsValid() then self.PlayerOwnerHash = pac.Hash(ply) else self.PlayerOwnerHash = nil end if owner_id then pac.RemoveUniqueIDPart(owner_id, self.UniqueID) end local owner_id = self:GetPlayerOwnerId() if owner_id then pac.SetUniqueIDPart(owner_id, self.UniqueID, self) end end -- always return the root owner function PART:GetPlayerOwner() return self:GetRootPart().PlayerOwner end function PART:GetPlayerOwnerId() return self:GetRootPart().PlayerOwnerHash end function PART:SetRootOwnerDeprecated(b) if b then self:SetTargetEntity(self:GetRootPart()) self.RootOwner = false if pace then pace.Call("VariableChanged", self, "TargetEntityUID", self:GetTargetEntityUID(), 0.25) end end end function PART:GetParentOwner() if self.TargetEntity:IsValid() and self.TargetEntity ~= self then return self.TargetEntity:GetOwner() end for _, parent in ipairs(self:GetParentList()) do -- legacy behavior if parent.ClassName == "event" and not parent.RootOwner then local parent = parent:GetParent() if parent:IsValid() then local parent = parent:GetParent() if parent:IsValid() then return parent:GetOwner() end end end if parent ~= self then local owner = parent:GetOwner() if owner:IsValid() then return owner end end end return NULL end function PART:GetOwner() if self.Owner:IsValid() then return self.Owner end return self:GetParentOwner() end end do -- scene graph function PART:OnParent() end function PART:OnChildAdd() end function PART:OnUnParent() end function PART:OnOtherPartCreated(part) local owner_id = part:GetPlayerOwnerId() if not owner_id then return end -- this will handle cases like if a part is removed and added again for _, key in pairs(self.PartReferenceKeys) do if self[key] and self[key].UniqueID == part.UniqueID then self["Set" .. key](self, part) end end do if not self.unresolved_uid_parts then return end if not self.unresolved_uid_parts[owner_id] then return end local keys = self.unresolved_uid_parts[owner_id][part.UniqueID] if not keys then return end for _, key in pairs(keys) do self["Set" .. key](self, part) end end end function PART:CreatePart(name) local part = pac.CreatePart(name, self:GetPlayerOwner()) if not part then return end part:SetParent(self) return part end function PART:SetDrawOrder(num) self.DrawOrder = num if self:HasParent() then self:GetParent():SortChildren() end end do -- children function PART:GetChildren() return self.Children end local function add_recursive(part, tbl, index) local source = part.Children for i = 1, #source do tbl[index] = source[i] index = index + 1 index = add_recursive(source[i], tbl, index) end return index end function PART:GetChildrenList() if not self.children_list then local tbl = {} add_recursive(self, tbl, 1) self.children_list = tbl end return self.children_list end function PART:InvalidateChildrenList() self.children_list = nil for _, parent in ipairs(self:GetParentList()) do parent.children_list = nil end end end do -- parent function PART:SetParent(part) if not part or not part:IsValid() then self:UnParent() return false else return part:AddChild(self) end end local function quick_copy(input) local output = {} for i = 1, #input do output[i + 1] = input[i] end return output end function PART:GetParentList() if not self.parent_list then if self.Parent and self.Parent:IsValid() then self.parent_list = quick_copy(self.Parent:GetParentList()) self.parent_list[1] = self.Parent else self.parent_list = {} end end return self.parent_list end function PART:InvalidateParentList() self.parent_list = nil for _, child in ipairs(self:GetChildrenList()) do child.parent_list = nil end end function PART:InvalidateParentListPartial(parent_list, parent) self.parent_list = quick_copy(parent_list) self.parent_list[1] = parent for _, child in ipairs(self:GetChildren()) do child:InvalidateParentListPartial(self.parent_list, self) end end end function PART:AddChild(part, ignore_show_hide) if not part or not part:IsValid() then self:UnParent() return end if self == part or part:HasChild(self) then return false end if part:HasParent() then part:UnParent() end part.Parent = self if not part:HasChild(self) then self.ChildrenMap[part] = part table_insert(self.Children, part) end self:InvalidateChildrenList() part.ParentUID = self:GetUniqueID() part:OnParent(self) self:OnChildAdd(part) if self:HasParent() then self:GetParent():SortChildren() end -- why would we need to sort part's children -- if it is completely unmodified? part:SortChildren() self:SortChildren() part:InvalidateParentListPartial(self:GetParentList(), self) if self:GetPlayerOwner() == pac.LocalPlayer then pac.CallHook("OnPartParent", self, part) end if not ignore_show_hide then part:CallRecursive("CalcShowHide", true) end return part.Id end do local function sort(a, b) return a.DrawOrder < b.DrawOrder end function PART:SortChildren() table.sort(self.Children, sort) self:InvalidateChildrenList() end end function PART:HasParent() return self.Parent:IsValid() end function PART:HasChildren() return self.Children[1] ~= nil end function PART:HasChild(part) return self.ChildrenMap[part] ~= nil end function PART:RemoveChild(part) self.ChildrenMap[part] = nil for i, val in ipairs(self:GetChildren()) do if val == part then self:InvalidateChildrenList() table.remove(self.Children, i) part:OnUnParent(self) break end end end function PART:GetRootPart() local list = self:GetParentList() if list[1] then return list[#list] end return self end function PART:CallRecursive(func, a,b,c) assert(c == nil, "EXTEND ME") if self[func] then self[func](self, a,b,c) end for _, child in ipairs(self:GetChildrenList()) do if child[func] then child[func](child, a,b,c) end end end function PART:CallRecursiveOnClassName(class_name, func, a,b,c) assert(c == nil, "EXTEND ME") if self[func] and self.ClassName == class_name then self[func](self, a,b,c) end for _, child in ipairs(self:GetChildrenList()) do if child[func] and self.ClassName == class_name then child[func](child, a,b,c) end end end function PART:SetKeyValueRecursive(key, val) self[key] = val for _, child in ipairs(self:GetChildrenList()) do child[key] = val end end function PART:RemoveChildren() self:InvalidateChildrenList() for i, part in ipairs(self:GetChildren()) do part:Remove(true) self.Children[i] = nil self.ChildrenMap[part] = nil end end function PART:UnParent() local parent = self:GetParent() if parent:IsValid() then parent:RemoveChild(self) end self:OnUnParent(parent) self.Parent = NULL self.ParentUID = "" self:CallRecursive("OnHide") end function PART:Remove(skip_removechild) self:Deattach() if not skip_removechild and self:HasParent() then self:GetParent():RemoveChild(self) end self:RemoveChildren() end function PART:Deattach() if not self.is_valid or self.is_deattached then return end self.is_deattached = true self.PlayerOwner_ = self.PlayerOwner if self:GetPlayerOwner() == pac.LocalPlayer then pac.CallHook("OnPartRemove", self) end self:CallRecursive("OnHide") self:CallRecursive("OnRemove") local owner_id = self:GetPlayerOwnerId() if owner_id then pac.RemoveUniqueIDPart(owner_id, self.UniqueID) end pac.RemovePart(self) self.is_valid = false self:InvalidateChildrenList() for _, part in ipairs(self:GetChildren()) do local owner_id = part:GetPlayerOwnerId() if owner_id then pac.RemoveUniqueIDPart(owner_id, part.UniqueID) end pac.RemovePart(part) end end end do -- hidden / events local pac_hide_disturbing = CreateClientConVar("pac_hide_disturbing", "1", true, true, "Hide parts which outfit creators marked as 'nsfw' (e.g. gore or explicit content)") function PART:SetIsDisturbing(val) self.IsDisturbing = val self.hide_disturbing = pac_hide_disturbing:GetBool() and val self:CallRecursive("CalcShowHide", true) end function PART:UpdateIsDisturbing() local new_value = pac_hide_disturbing:GetBool() and self.IsDisturbing if new_value == self.hide_disturbing then return end self.hide_disturbing = new_value self:CallRecursive("CalcShowHide", true) end function PART:OnHide() end function PART:OnShow() end function PART:SetEnabled(val) self.Enabled = val if val then self:ShowFromRendering() else self:HideFromRendering() end end function PART:SetHide(val) self.Hide = val -- so that IsHiddenCached works in OnHide/OnShow events self:SetKeyValueRecursive("last_hidden", val) if val then self:CallRecursive("OnHide", true) else self:CallRecursive("OnShow", true) end self:CallRecursive("CalcShowHide", true) end function PART:IsDrawHidden() return self.draw_hidden end function PART:SetDrawHidden(b) self.draw_hidden = b end function PART:ShowFromRendering() self:SetDrawHidden(false) if not self:IsHidden() then self:OnShow(true) end for _, child in ipairs(self:GetChildrenList()) do child:SetDrawHidden(false) if not child:IsHidden() then child:OnShow(true) end end end function PART:HideFromRendering() self:SetDrawHidden(true) self:CallRecursive("OnHide", true) end local function is_hidden(part) if part.active_events_ref_count > 0 then return true end return part.Hide or part.hide_disturbing end function PART:IsHidden() if is_hidden(self) then return true end for _, parent in ipairs(self:GetParentList()) do if is_hidden(parent) then return true end end return false end function PART:SetEventTrigger(event_part, enable) if enable then if not self.active_events[event_part] then self.active_events[event_part] = event_part self.active_events_ref_count = self.active_events_ref_count + 1 self:CallRecursive("CalcShowHide", false) end else if self.active_events[event_part] then self.active_events[event_part] = nil self.active_events_ref_count = self.active_events_ref_count - 1 self:CallRecursive("CalcShowHide", false) end end end function PART:GetReasonHidden() local found = {} for part in pairs(self.active_events) do table_insert(found, tostring(part) .. " is event hiding") end if found[1] then return table.concat(found, "\n") end if self.Hide then return "hide enabled" end if self.hide_disturbing then return "pac_hide_disturbing is set to 1" end return "" end function PART:CalcShowHide(from_rendering) local b = self:IsHidden() if b ~= self.last_hidden then if b then self:OnHide(from_rendering) else self:OnShow(from_rendering) end end self.last_hidden = b end function PART:IsHiddenCached() return self.last_hidden end function PART:BuildBonePositions() if not self.Enabled then return end if not self:IsHiddenCached() then self:OnBuildBonePositions() end end function PART:OnBuildBonePositions() end end PART.show_in_editor = true function PART:GetShowInEditor() return self:GetRootPart().show_in_editor == true end function PART:SetShowInEditor(b) self:GetRootPart().show_in_editor = b end do -- serializing function PART:AddStorableVar(var) self.StorableVars = self.StorableVars or {} self.StorableVars[var] = var end function PART:GetStorableVars() self.StorableVars = self.StorableVars or {} return self.StorableVars end function PART:Clear() self:RemoveChildren() end function PART:OnWorn() -- override end function PART:OnOutfitLoaded() -- override end function PART:PostApplyFixes() -- override end do function PART:GetProperty(name) local val = self["Get" .. name] if val == nil then if self.GetDynamicProperties then local info = self:GetDynamicProperties() if info and info[name] then return info[name].get() end end else return val(self) end end function PART:SetProperty(key, val) if self["Set" .. key] ~= nil then if self["Get" .. key](self) ~= val then self["Set" .. key](self, val) end elseif self.GetDynamicProperties then local info = self:GetDynamicProperties()[key] if info and info then info.set(val) end end end function PART:GetProperties() local out = {} for _, key in pairs(self:GetStorableVars()) do if self.PropertyWhitelist and not self.PropertyWhitelist[key] then goto CONTINUE end table_insert(out, { key = key, set = function(v) self["Set" .. key](self, v) end, get = function() return self["Get" .. key](self) end, udata = pac.GetPropertyUserdata(self, key) or {}, }) ::CONTINUE:: end if self.GetDynamicProperties then local props = self:GetDynamicProperties() if props then for _, info in pairs(props) do if not self.PropertyWhitelist or self.PropertyWhitelist[info.key] then table_insert(out, info) end end end end local sorted = {} local done = {} for _, key in ipairs({"Name", "Hide"}) do for _, prop in ipairs(out) do if key == prop.key then if not done[key] then table_insert(sorted, prop) done[key] = true break end end end end if pac.VariableOrder[self.ClassName] then for _, key in ipairs(pac.VariableOrder[self.ClassName]) do for _, prop in ipairs(out) do if key == prop.key then if not done[key] then table_insert(sorted, prop) done[key] = true break end end end end end for _, variables in pairs(pac.VariableOrder) do for _, key in ipairs(variables) do for _, prop in ipairs(out) do if key == prop.key then if not done[key] then table_insert(sorted, prop) done[key] = true break end end end end end for _, prop in ipairs(out) do if not done[prop.key] then table_insert(sorted, prop) end end return sorted end end local function on_error(msg) ErrorNoHalt(debug_traceback(msg)) end do local function SetTable(self, tbl, level) self:SetUniqueID(tbl.self.UniqueID) self.delayed_variables = self.delayed_variables or {} for key, value in next, tbl.self do if key == "UniqueID" then goto CONTINUE end if self["Set" .. key] then if key == "Material" then table_insert(self.delayed_variables, {key = key, val = value}) end self["Set" .. key](self, value) elseif key ~= "ClassName" then pac.dprint("settable: unhandled key [%q] = %q", key, tostring(value)) end ::CONTINUE:: end for _, value in pairs(tbl.children) do local part = pac.CreatePart(value.self.ClassName, self:GetPlayerOwner(), value, nil --[[make_copy]], level + 1) self:AddChild(part, true) end end local function make_copy(tbl, pepper, uid_list) if pepper == true then pepper = tostring(math_random()) .. tostring(math_random()) end uid_list = uid_list or {} tbl.self.UniqueID = pac.Hash(tbl.self.UniqueID .. pepper) uid_list[tostring(tbl.self.UniqueID)] = tbl.self for _, child in ipairs(tbl.children) do make_copy(child, pepper, uid_list) end return tbl, pepper, uid_list end local function update_uids(uid_list, pepper) for uid, part in pairs(uid_list) do for key, val in pairs(part) do if (key:sub(-3) == "UID") then local new_uid = pac.Hash(val .. pepper) if uid_list[tostring(new_uid)] then part[key] = new_uid end end end end end function PART:SetTable(tbl, copy_id, level) level = level or 0 if copy_id then local pepper, uid_list tbl, pepper, uid_list = make_copy(table.Copy(tbl), copy_id) update_uids(uid_list, pepper) end local ok, err = xpcall(SetTable, on_error, self, tbl, level) if not ok then pac.Message(Color(255, 50, 50), "SetTable failed: ", err) end -- figure out if children needs to be hidden if level == 0 then self:CallRecursive("CalcShowHide", true) end end end function PART:ToTable() local tbl = {self = {ClassName = self.ClassName}, children = {}} for _, key in pairs(self:GetStorableVars()) do local var = self[key] and self["Get" .. key](self) or self[key] var = pac.CopyValue(var) or var if make_copy_name and var ~= "" and (key == "UniqueID" or key:sub(-3) == "UID") then var = pac.Hash(var .. var) end if key == "Name" and self[key] == "" then var = "" end -- these arent needed because parent system uses the tree structure if key ~= "ParentUID" and var ~= self.DefaultVars[key] then tbl.self[key] = var end end for _, part in ipairs(self:GetChildren()) do if not self.is_valid or self.is_deattached then else table_insert(tbl.children, part:ToTable()) end end return tbl end function PART:ToSaveTable() if self:GetPlayerOwner() ~= pac.LocalPlayer then return end local tbl = {self = {ClassName = self.ClassName}, children = {}} for _, key in pairs(self:GetStorableVars()) do local var = self[key] and self["Get" .. key](self) or self[key] var = pac.CopyValue(var) or var if key == "Name" and self[key] == "" then var = "" end -- these arent needed because parent system uses the tree structure if key ~= "ParentUID" then tbl.self[key] = var end end for _, part in ipairs(self:GetChildren()) do if not self.is_valid or self.is_deattached then else table_insert(tbl.children, part:ToSaveTable()) end end return tbl end do -- undo do local function SetTable(self, tbl) self:SetUniqueID(tbl.self.UniqueID) self.delayed_variables = self.delayed_variables or {} for key, value in pairs(tbl.self) do if key == "UniqueID" then goto CONTINUE end if self["Set" .. key] then if key == "Material" then table_insert(self.delayed_variables, {key = key, val = value}) end self["Set" .. key](self, value) elseif key ~= "ClassName" then pac.dprint("settable: unhandled key [%q] = %q", key, tostring(value)) end ::CONTINUE:: end for _, value in pairs(tbl.children) do local part = pac.CreatePart(value.self.ClassName, self:GetPlayerOwner()) part:SetUndoTable(value) part:SetParent(self) end end function PART:SetUndoTable(tbl) local ok, err = xpcall(SetTable, on_error, self, tbl) if not ok then pac.Message(Color(255, 50, 50), "SetUndoTable failed: ", err) end end end function PART:ToUndoTable() if self:GetPlayerOwner() ~= pac.LocalPlayer then return end local tbl = {self = {ClassName = self.ClassName}, children = {}} for _, key in pairs(self:GetStorableVars()) do if key == "Name" and self.Name == "" then -- TODO: seperate debug name and name !!! goto CONTINUE end tbl.self[key] = pac.CopyValue(self["Get" .. key](self)) ::CONTINUE:: end for _, part in ipairs(self:GetChildren()) do if not self.is_valid or self.is_deattached then else table_insert(tbl.children, part:ToUndoTable()) end end return tbl end end function PART:GetVars() local tbl = {} for _, key in pairs(self:GetStorableVars()) do tbl[key] = pac.CopyValue(self[key]) end return tbl end function PART:Clone() local part = pac.CreatePart(self.ClassName, self:GetPlayerOwner()) if not part then return end -- ugly workaround for cloning bugs if self:GetOwner() == self:GetPlayerOwner() then part:SetOwner(self:GetOwner()) end part:SetTable(self:ToTable(), true) if self:GetParent():IsValid() then part:SetParent(self:GetParent()) end return part end end do function PART:Think() if not self.Enabled then return end if self.ThinkTime ~= 0 and self.last_think and self.last_think > pac.RealTime then return end if not self.AlwaysThink and self:IsHiddenCached() then self:AlwaysOnThink() -- for things that drive general logic -- such as processing outfit URL downloads -- without calling probably expensive self:OnThink() return end if self.delayed_variables then for _, data in ipairs(self.delayed_variables) do self["Set" .. data.key](self, data.val) end self.delayed_variables = nil end self:AlwaysOnThink() -- for things that drive general logic -- such as processing outfit URL downloads -- without calling probably expensive self:OnThink() self:OnThink() end function PART:OnThink() end function PART:AlwaysOnThink() end end BUILDER:Register()