--[[ | 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 L = pace.LanguageString pace.Tools = {} function pace.AddToolsToMenu(menu) menu.GetDeleteSelf = function() return false end for key, data in pairs(pace.Tools) do if #data.suboptions > 0 then local menu = menu:AddSubMenu(L(data.name)) menu.GetDeleteSelf = function() return false end for key, option in pairs(data.suboptions) do menu:AddOption(option, function() if pace.current_part:IsValid() then data.callback(pace.current_part, key) end end) end else menu:AddOption(L(data.name), function() if pace.current_part:IsValid() then data.callback(pace.current_part) end end) end end end function pace.AddTool(name, callback, ...) for i,v in pairs(pace.Tools) do if v.name == name then table.remove(pace.Tools, i) end end table.insert(pace.Tools, {name = name, callback = callback, suboptions = {...}}) end pace.AddTool(L"convert legacy parts to new parts", function(part, suboption) local class_translate = { model = "model2", material = "material_3d", entity = "entity2", bone = "bone3", bone2 = "bone3", light = "light2", trail = "trail2", clip = "clip2", } local model_prop_translate = { Color = function(tbl, val) tbl.Color = Vector(val.r/255, val.g/255, val.b/255) end, DoubleFace = function(tbl, val) tbl.NoCulling = val end, Fullbright = function(tbl, val) tbl.NoLighting = val end, TextureFilter = function(tbl, val) tbl.NoTextureFiltering = not val end, LodOverride = function(tbl, val) tbl.LevelOfDetail = val end, } local registered_parts = pac.GetRegisteredParts() local bones = {} local material_translate = {} for old_key in pairs(registered_parts.material.ShaderParams) do local new_key = old_key:lower() local new_val = registered_parts.material_3d[new_key] local old_val = registered_parts.material[old_key] if new_val ~= nil and type(new_val) == type(old_val) then material_translate[old_key] = function(tbl, val) tbl[new_key] = val end end end local prop_translate = { model = model_prop_translate, entity = table.Merge(model_prop_translate, { HideEntity = function(tbl, val) tbl.NoDraw = val end }), material = material_translate, bone = { Bone = function(tbl, val) if bones[tbl.UniqueID] and not bones[tbl.UniqueID][val] then for k,v in pairs(bones[tbl.UniqueID]) do if v.bone == 0 then tbl.Bone = v.friendly return end end end tbl.Bone = val end, } } local function calc_manip_offset(ent, bone, pos, ang) if not ent:GetBoneMatrix(bone) then return end local prev_pos = ent:GetManipulateBonePosition(bone) local prev_ang = ent:GetManipulateBoneAngles(bone) ent:ManipulateBonePosition(bone, Vector()) ent:ManipulateBoneAngles(bone, Angle()) pac.SetupBones(ent) local pre = ent:GetBoneMatrix(bone) ent:ManipulateBonePosition(bone, pos) ent:ManipulateBoneAngles(bone, ang) pac.SetupBones(ent) local post = ent:GetBoneMatrix(bone) ent:ManipulateBonePosition(bone, prev_pos) ent:ManipulateBoneAngles(bone, prev_ang) local m = pre:GetInverseTR() * post return m:GetTranslation(), m:GetAngles() end local saved = {} for _, part in pairs(pac.GetLocalParts()) do if part.ClassName == "bone" or part.ClassName == "bone2" then bones[part.UniqueID] = pac.GetAllBones(part:GetOwner()) if part:GetModelBoneIndex() and not part:GetAlternativeBones() then local pos, ang = calc_manip_offset(part:GetOwner(), part:GetModelBoneIndex(), part:GetPosition(), part:GetAngles()) part:SetPosition(pos) part:SetAngles(ang) end end if not part:HasParent() then table.insert(saved, part:ToTable()) end end pace.ClearParts() local done = {} local function walk(tbl, parent) local new_classname = class_translate[tbl.self.ClassName] if new_classname then local old_classname = tbl.self.ClassName tbl.self.ClassName = new_classname if old_classname == "model" then if tbl.self.BlurLength and tbl.self.BlurLength > 0 then table.insert(tbl.children, { self = { ClassName = "motion_blur", UniqueID = pac.Hash(), BlurLength = tbl.self.BlurLength, BlurSpacing = tbl.self.BlurSpacing, }, children = {}, }) end end if old_classname == "entity" then if tbl.self.Weapon == true and tbl.self.HideEntity then table.insert(parent.children, { self = { ClassName = "weapon", UniqueID = pac.Hash(), NoDraw = true }, children = {}, }) end end for key in pairs(registered_parts[old_classname].StorableVars) do local value = tbl.self[key] if value == nil then value = registered_parts[old_classname][key] end if prop_translate[old_classname] and prop_translate[old_classname][key] then tbl.self[key] = nil prop_translate[old_classname][key](tbl.self, value) if value ~= tbl.self[key] then pac.Message("translated property: ", key, " = ", value, " to ", tbl.self[key]) end elseif not registered_parts[new_classname].StorableVars[key] then local msg = tbl.self.ClassName .. "." .. key if not done[msg] then pac.Message(Color(255,100,100), "cannot translate property ", msg) done[msg] = true end end end end if tbl.self.ClassName == "proxy" and tbl.self.VariableName == "Color" then if isstring(tbl.self.Expression) and tbl.self.Expression ~= "" then local r,g,b = unpack(tbl.self.Expression:Split(",")) r = tonumber(r) g = tonumber(g) b = tonumber(b) if r and g and b then tbl.self.Expression = (r/255)..","..(g/255)..","..(b/255) end end end if tbl.self.Model and tbl.self.Model:find("://", nil, true) and not tbl.self.Model:find(".zip", nil, true) then tbl.self.ForceObjUrl = true end if tbl.children then for _, child in ipairs(tbl.children) do walk(child, tbl) end end end for _, tbl in ipairs(saved) do walk(tbl) end for _, tbl in ipairs(saved) do local part = pac.CreatePart(tbl.self.ClassName, pac.LocalPlayer, tbl) end end) pace.AddTool(L"fix origin", function(part, suboption) if not part.GetEntity then return end local ent = part:GetOwner() part:SetPositionOffset(-ent:OBBCenter() * part.Scale * part.Size) end) pace.AddTool(L"replace ogg with webaudio", function(part, suboption) for _, part in pairs(pac.GetLocalParts()) do if part.ClassName == "ogg" then local parent = part:GetParent() local audio = pac.CreatePart("webaudio") audio:SetParent(parent) audio:SetURL(part:GetURL()) audio:SetVolume(part:GetVolume()) audio:SetPitch(part:GetPitch()) audio:SetStopOnHide(not part:GetStopOnHide()) audio:SetPauseOnHide(part:GetPauseOnHide()) for k,v in ipairs(part:GetChildren()) do v:SetParent(audio) end part:Remove() end end end) pace.AddTool(L"copy global id", function(obj) SetClipboardText("\"" .. obj.UniqueID .. "\"") end) pace.AddTool(L"use legacy scale", function(part, suboption) for _, part in pairs(pac.GetLocalParts()) do if part.UseLegacyScale ~= nil then part:SetUseLegacyScale(suboption == 1) end end end, L"true", L"false") pace.AddTool(L"scale this and children", function(part, suboption) Derma_StringRequest(L"scale", L"input the scale multiplier (does not work well with bones)", "1", function(scale) scale = tonumber(scale) if scale and part:IsValid() then local function scale_parts(part, scale) if part.SetPosition then part:SetPosition(part:GetPosition() * scale) part:SetPositionOffset(part:GetPositionOffset() * scale) end if part.SetSize then part:SetSize(part:GetSize() * scale) end for _, part in ipairs(part:GetChildren()) do scale_parts(part, scale) end end scale_parts(part, scale) end end) end) pace.AddTool(L"free children from part" ,function(part, suboption) if part:IsValid() then local children = part.Children if #children == 0 then Derma_Message(L"this part has no children...", L"free children from part", "ok") return end Derma_Query(L"this process cannot be undone, are you sure?", L"free children from part", L"yes", function() local grandparent = part:GetParent() if grandparent == NULL then grandparent = part:GetRootPart() end local parent = part for _, child in ipairs(children) do if child.BaseName ~= "base" and parent.BaseName ~= "base" then child:SetAngles(child.Angles + parent.Angles) child:SetPosition(child.Position + parent.Position) child:SetAngleOffset(child.AngleOffset + parent.AngleOffset) child:SetPositionOffset(child.PositionOffset + parent.PositionOffset) end child:SetParent(grandparent) end end, L"no", function() end) end end) pace.AddTool(L"square model scales...", function(part, suboption) Derma_StringRequest(L"model", L"input the model name that should get squared", "default.mdl", function(model) for _, part in pairs(pac.GetLocalParts()) do if part:IsValid() and part.GetModel then local function square_scale(part) if part.SetSize then part:SetSize(part:GetSize() * part:GetSize()) end if part.SetScale then part:SetScale(part:GetScale() * part:GetScale()) end end if string.find(part:GetModel(),model) then square_scale(part) end end end end) end) pace.AddTool(L"show only with active weapon", function(part, suboption) local event = part:CreatePart("event") local owner = part:GetRootPart():GetOwner() if not owner.GetActiveWeapon or not owner:GetActiveWeapon():IsValid() then owner = pac.LocalPlayer end local class_name = owner:GetActiveWeapon():GetClass() event:SetEvent("weapon_class") event:SetOperator("equal") event:SetInvert(true) event:SetRootOwner(true) event:ParseArguments(class_name, suboption == 1) end, L"hide weapon", L"show weapon") pace.AddTool(L"import editor tool from file...", function() local allowcslua = GetConVar("sv_allowcslua") if allowcslua:GetBool() then Derma_StringRequest(L"filename", L"relative to garrysmod/data/pac3_editor/tools/", "mytool.txt", function(toolfile) if file.Exists("pac3_editor/tools/" .. toolfile,"DATA") then local toolstr = file.Read("pac3_editor/tools/" .. toolfile,"DATA") local ctoolstr = [[pace.AddTool(L"]] .. toolfile .. [[", function(part, suboption) ]] .. toolstr .. " end)" RunStringEx(ctoolstr, "pac_editor_import_tool") pac.LocalPlayer:ConCommand("pac_editor") --close and reopen editor else Derma_Message("File " .. "garrysmod/data/pac3_editor/tools/" .. toolfile .. " not found.","Error: File Not Found","OK") end end) else Derma_Message("Importing pac editor tools is disallowed on this server.","Error: Clientside Lua Disabled","OK") end end) pace.AddTool(L"import editor tool from url...", function() if GetConVar("sv_allowcslua"):GetBool() then Derma_StringRequest(L"URL", L"URL to PAC Editor tool txt file", "http://www.example.com/tool.txt", function(toolurl) local function ToolDLSuccess(body) local toolname = pac.PrettifyName(toolurl:match(".+/(.-)%.")) local toolstr = body local ctoolstr = [[pace.AddTool(L"]] .. toolname .. [[", function(part, suboption)]] .. toolstr .. " end)" RunStringEx(ctoolstr, "pac_editor_import_tool") pac.LocalPlayer:ConCommand("pac_editor") --close and reopen editor end pac.HTTPGet(toolurl,ToolDLSuccess,function(err) pace.MessagePrompt(err, "HTTP Request Failed for " .. toolurl, "OK") end) end) else Derma_Message("Importing pac editor tools is disallowed on this server.","Error: Clientside Lua Disabled","OK") end end) local function round_pretty(val) return math.Round(val, 2) end pace.AddTool(L"round numbers", function(part) local function ify_parts(part) for _, key in pairs(part:GetStorableVars()) do local val = part["Get" .. key](part) if isnumber(val) then part["Set" .. key](part, round_pretty(val)) elseif isvector(val) then part["Set" .. key](part, Vector(round_pretty(val.x), round_pretty(val.y), round_pretty(val.z))) elseif isangle(val) then part["Set" .. key](part, Angle(round_pretty(val.p), round_pretty(val.y), round_pretty(val.r))) end end for _, part in ipairs(part:GetChildren()) do ify_parts(part) end end ify_parts(part) end) do local function fix_name(str) str = str:lower() str = str:gsub("_", " ") return str end local hue = { "red", "orange", "yellow", "green", "turquoise", "blue", "purple", "magenta", } local sat = { "pale", "", "strong", } local val = { "dark", "", "bright" } local function HSVToNames(h,s,v) return hue[math.Round(1 + (h / 360) * #hue)] or hue[1], sat[math.ceil(s * #sat)] or sat[1], val[math.ceil(v * #val)] or val[1] end local function ColorToNames(c) return HSVToNames(ColorToHSV(Color(c.r, c.g, c.b))) end pace.AddTool(L"clear names", function(part, suboptions) for k,v in pairs(pac.GetLocalParts()) do v:SetName("") end pace.RefreshTree(true) end) end pace.AddTool(L"Convert group of models to Expression 2 holograms", function(part) local str_ref = [[ I++, HN++, HT[HN,table] = table(I, Base, Base, 0, POSITION, ANGLES, SCALE, MODEL, MATERIAL, vec4(COLOR, ALPHA), SKIN) ]] local str_header = [[ @name [NAME] #- Entity input directives @inputs [Base]:entity #- Spawning code directives @persist [HT CT]:table [SpawnStatus CoreStatus]:string [HN CN I SpawnCounter] @persist [ScaleFactor ToggleColMat ToggleShading] Indices @persist [DefaultColor DefaultScale]:vector #- Hologram index directives @persist if (first() | dupefinished()) { Chip = entity(), Base = entity() #- Remove to use wired entity. ScaleFactor = 1 #- Scale multiplier. ToggleColMat = 1 #- Toggle for materials and colours. ToggleShading = 0 #- Toggle for shading. Indices = 1 #- Data structure #- HN++, HT[HN, table] = table(Index, Local Entity (Entity:toWorld()), Parent Entity, ScaleType (Default 0), Pos, Ang, Scale, Model, Material, Color, Skin) #- CN++, CT[CN, table] = table(Index, Clip Index, Pos, Ang) #- Editing holograms #- Scroll down to the bottom of the code to find where to insert your holo() code. In order to reference indexes #- add a ", I_HologramName"" to the end of that holograms data line with "HologramName" being of your choosing. #- Finally add this to a @persist directive eg "@persist [I_HologramName]", now you can address this in your holo() code. #- For example, "holoBodygroup(I_HologramName, 2, 3)" which would be put in the "InitPostSpawn" section. #- Advanced functionality #- If you wish to take this system to the next level, you can. Instead of using multiple e2s for each "set" of holograms, #- instead save each set of hologram data to a new file inside a folder of your liking. You can now use the #include "" directive #- to bring that hologram data into a single e2 with this spawn code and compile multiple files into a single e2. #- This has many benefits such as 1 interval instead of many, auto updating due to the chip pulling saved data and increased #- organisation! #- Your file hierarchy should look like this. #- /expression2/ #- --> /yourfolder/ #- --> /hologram_data.txt & hologram_spawner.txt # # # # # # # # # HOLOGRAM DATA START # # # # # # # # # ]] local str_footer = [[ # # # # # # # # # HOLOGRAM DATA END # # # # # # # # # #- Create a hologram from data array function table:holo() { local Index = This[1, number] * Indices if (This[2,entity]:isValid()) { Entity = This[2,entity] } else { Entity = holoEntity(This[2,number]) } if (This[3,entity]:isValid()) { Parent = This[3,entity] } else { Parent = holoEntity(This[3,number]) } local Rescale = (This[7, vector] / (This[4, number] ? 12 : 1)) * ScaleFactor holoCreate(Index, Entity:toWorld(This[5, vector] * ScaleFactor), Rescale, Entity:toWorld(This[6, angle]), DefaultColor, This[8, string] ?: "cube") holoParent(Index, Parent) if (ToggleColMat) { holoMaterial(Index, This[9, string]) holoColor(Index, This[10, vector4]) holoSkin(Index, This[11, number]) } if (ToggleShading) { holoDisableShading(Index, 1) } } #- Clip a hologram from data array function table:clip() { holoClipEnabled(This[1, number] * Indices, This[2, number], 1) holoClip(This[1, number] * Indices, This[2, number], This[3, vector] * ScaleFactor, This[4, vector], 0) } #- Load the contraption function loadContraption() { switch (SpawnStatus) { case "InitSpawn", if (clk("Start")) { SpawnStatus = "LoadHolograms" } Chip:soundPlay("Blip", 0, "@^garrysmod/content_downloaded.wav", 0.212) break case "LoadHolograms", while (perf() & holoCanCreate() & SpawnCounter < HN) { SpawnCounter++ HT[SpawnCounter, table]:holo() if (SpawnCounter >= HN) { SpawnStatus = CN > 0 ? "LoadClips" : "PrintStatus" SpawnCounter = 0 break } } break case "LoadClips", while (perf() & SpawnCounter < CN) { SpawnCounter++ CT[SpawnCounter, table]:clip() if (SpawnCounter >= CN) { SpawnStatus = "PrintStatus" SpawnCounter = 0 break } } break case "PrintStatus", printColor( vec(222,37,188), "PAC to Holo: ", vec(255,255,255), "Loaded " + HN + " holograms and " + CN + " clips." ) HT:clear() CT:clear() CoreStatus = "InitPostSpawn" SpawnStatus = "" break } } CoreStatus = "InitSpawn" SpawnStatus = "InitSpawn" DefaultColor = vec(255, 255, 255) runOnTick(1) timer("Start", 500) } #- Credit to Shadowscion for the initial base hologram spawning code. elseif (CoreStatus == "InitSpawn") { loadContraption() } elseif (CoreStatus == "InitPostSpawn") { #- This is your "if (first())" section of the code. CoreStatus = "RunThisCode" } elseif (CoreStatus == "RunThisCode") { #- This is your "interval()" ran section of the code. runOnTick(0) }]] local function tovec(vec) return ("%s, %s, %s"):format(math.Round(vec.x, 4), math.Round(vec.y, 4), math.Round(vec.z, 4)) end local function toang(vec) return ("%s, %s, %s"):format(math.Round(vec.p, 4), math.Round(vec.y, 4), math.Round(vec.r, 4)) end local function part_to_holo(part) local str_holo = str_ref for CI, clip in ipairs(part:GetChildren()) do if not clip:IsHidden() then if clip.ClassName == "clip" then local pos, ang = clip.Position, clip:CalcAngles(clip.Angles) local normal = ang:Forward() str_holo = str_holo .. " CN++, CT[CN,table] = table(I, " .. CI .. ", vec(" .. tovec(pos + normal) .. "), vec(" .. tovec(normal) .. "))\n" elseif clip.ClassName == "clip2" then local pos, ang = clip.Position, clip:CalcAngles(clip.Angles) local normal = ang:Forward() str_holo = str_holo .. " CN++, CT[CN,table] = table(I, " .. CI .. ", vec(" .. tovec(pos) .. "), vec(" .. tovec(normal) .. "))\n" end end end local scale = part:GetSize() * part:GetScale() local pos, ang = part.Position, part.Angles if not part.PositionOffset:IsZero() or not part.AngleOffset:IsZero() then pos, ang = LocalToWorld(part.PositionOffset, part.AngleOffset, pos, ang) end local holo = str_holo:gsub("[A-Z]+",{ ALPHA = math.Round(part:GetAlpha() * 255, 4), COLOR = tovec((part.ProperColorRange and part:GetColor()*255) or part:GetColor()), SCALE = "vec(" .. tovec(Vector(scale.x, scale.y, scale.z)) .. ")", ANGLES = "ang(" .. toang(ang) .. ")", POSITION = "vec(" .. tovec(pos) .. ")", MATERIAL = ("%q"):format(part:GetMaterial()), MODEL = ("%q"):format(part:GetModel()), SKIN = part.GetSkin and part:GetSkin() or "0", PARENT = "entity()" }) return holo end local function convert(part) local out = {string.Replace(str_header, "[NAME]", part:GetName() or "savedpacholos")} local completed = {} local function recursiveConvert(parent) if completed[parent] then return end completed[parent] = true for key, part in ipairs(parent:GetChildren()) do if part.is_model_part and not part:IsHidden() then out[#out + 1] = part_to_holo(part) recursiveConvert(part) end end end recursiveConvert(part) out[#out + 1] = str_footer pac.LocalPlayer:ChatPrint("PAC --> Code saved in your Expression 2 folder under [expression2/pac/" .. part:GetName() .. ".txt" .. "].") return table.concat(out) end file.CreateDir("expression2/pac") file.Write("expression2/pac/" .. part:GetName() .. ".txt", convert(part)) end) pace.AddTool(L"record surrounding props to pac", function(part) local base = pac.CreatePart("group") base:SetName("recorded props") local origin = base:CreatePart("model") origin:SetName("origin") origin:SetBone("none") origin:SetModel("models/dav0r/hoverball.mdl") for key, ent in pairs(ents.FindInSphere(pac.EyePos, 1000)) do if not ent:IsPlayer() and not ent:IsNPC() and not ent:GetOwner():IsPlayer() then local mdl = origin:CreatePart("model") mdl:SetModel(ent:GetModel()) local lpos, lang = WorldToLocal(ent:GetPos(), ent:GetAngles(), pac.EyePos, pac.EyeAng) mdl:SetMaterial(ent:GetMaterial()) mdl:SetPosition(lpos) mdl:SetAngles(lang) local c = ent:GetColor() mdl:SetColor(Vector(c.r,c.g,c.b)) mdl:SetAlpha(c.a / 255) mdl:SetName(ent:GetModel():match(".+/(.-)%.mdl")) end end end) pace.AddTool(L"populate with bones", function(part,suboption) local ent = part:GetOwner() local bones = pac.GetModelBones(ent) for bone,tbl in pairs(bones) do if not tbl.is_special then local child = pac.CreatePart("bone3") child:SetParent(part) child:SetBone(bone) end end pace.RefreshTree(true) end) pace.AddTool(L"populate with dummy bones", function(part,suboption) local ent = part:GetOwner() local bones = pac.GetModelBones(ent) for bone,tbl in pairs(bones) do if not tbl.is_special then local child = pac.CreatePart("model") child:SetParent(part) child:SetName(bone .. "_dummy") child:SetBone(bone) child:SetScale(Vector(0,0,0)) end end pace.RefreshTree(true) end) pace.AddTool(L"print part info", function(part) PrintTable(part:ToTable()) end) pace.AddTool(L"dump player submaterials", function() local ply = pac.LocalPlayer for id,mat in pairs(ply:GetMaterials()) do chat.AddText(("%d %s"):format(id,tostring(mat))) end end) pace.AddTool(L"stop all custom animations", function() pac.animations.StopAllEntityAnimations(pac.LocalPlayer) pac.animations.ResetEntityBoneMatrix(pac.LocalPlayer) end) pace.AddTool(L"copy from faceposer tool", function(part, suboption) local group = pac.CreatePart("group") local ent = pac.LocalPlayer for i = 0, ent:GetFlexNum() - 1 do local name = ent:GetFlexName(i) local fp_flex = GetConVar("faceposer_flex" .. i):GetFloat() local fp_scale = GetConVar("faceposer_scale"):GetFloat() local weight = fp_flex * fp_scale pac.Message(name, weight) if weight ~= 0 then local flex = group:CreatePart("flex") flex:SetFlex(name) flex:SetWeight(weight) end end end)