--[[ | 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/ --]] --[[ Smart Weld Created by: Stalker (STEAM_0:1:18093014) - Contact for support Originally by Duncan Stead (Dunk) - Dont contact for support ]] TOOL.AllowedClasses = { prop_physics = true, prop_physics_multiplayer = true, prop_ragdoll = true, prop_effect = true, prop_vehicle = true, prop_vehicle_jeep = true, prop_vehicle_airboat = true, prop_vehicle_apc = true, prop_vehicle_crane = true, prop_vehicle_prisoner_pod = true } TOOL.AllowedBaseClasses = { base_anim = true, base_entity = true, base_gmodentity = true, base_wire_entity = true, -- Wiremod sent_sakarias_scar_base = true, -- SCars base_rd3_entity = true -- Spacebuild } TOOL.Category = "Constraints" TOOL.Name = "Weld - Smart" TOOL.ClientConVar["selectradius"] = 100 TOOL.ClientConVar["nocollide"] = 1 TOOL.ClientConVar["freeze"] = 0 TOOL.ClientConVar["clearwelds"] = 1 TOOL.ClientConVar["strength"] = 0 TOOL.ClientConVar["world"] = 0 TOOL.ClientConVar["maxweldsperprop"]= 10 -- Only for when you weld more than 127 props at once TOOL.ClientConVar["color_r"] = 0 TOOL.ClientConVar["color_g"] = 255 TOOL.ClientConVar["color_b"] = 0 TOOL.ClientConVar["color_a"] = 255 TOOL.SelectedProps = {} -- These don't exist on the server in singleplayer but we need them there. if game.SinglePlayer() then NOTIFY_GENERIC = 0 NOTIFY_ERROR = 1 NOTIFY_UNDO = 2 NOTIFY_HINT = 3 NOTIFY_CLEANUP = 4 end cleanup.Register("smartweld") if CLIENT then TOOL.Information = { {name = "left"}, {name = "leftuse"}, {name = "right", stage = 2}, {name = "rightuse", stage = 2}, {name = "reload", stage = 2}, } language.Add("tool.smartweld.name", "Weld - Smart") language.Add("tool.smartweld.desc", "Automatically welds selected props") language.Add("tool.smartweld.left", "Select or deselect a prop") language.Add("tool.smartweld.leftuse", "Auto-Selects the props in a set radius") language.Add("tool.smartweld.right", "Welds the selected props") language.Add("tool.smartweld.rightuse", "Welds all the props to the one you\'re looking at") language.Add("tool.smartweld.reload", "Clears the current selection") language.Add("tool.smartweld.selectoutsideradius", "Auto-Select Radius:") language.Add("tool.smartweld.selectoutsideradius.help", "The auto-select radius, anything beyond this value wont be selected.") language.Add("tool.smartweld.maxweldsperprop", "Max welds per prop") language.Add("tool.smartweld.maxweldsperprop.help", "The maximum welds per prop. This only works if you are welding more than 127 props at once. Higher than 10 not recommended, 15 maximum.") language.Add("tool.smartweld.strength", "Force Limit:") language.Add("tool.smartweld.strength.help", "The strength of the welds created. Use 0 for unbreakable welds.") language.Add("tool.smartweld.world", "Weld everything to world") language.Add("tool.smartweld.world.help", "Turning this on will weld everything to the world. Useful for making something totally immovable.") language.Add("tool.smartweld.nocollide", "No-collide") language.Add("tool.smartweld.nocollide.help", "Whether all props should no-collide each other when welded.") language.Add("tool.smartweld.freeze", "Auto-freeze") language.Add("tool.smartweld.freeze.help", "Whether all selected props should be frozen after the weld.") language.Add("tool.smartweld.clearwelds", "Remove old welds") language.Add("tool.smartweld.clearwelds.help", "If a selected prop has any welds already on it this will remove them first.") language.Add("tool.smartweld.color", "Selection color") language.Add("tool.smartweld.color.help", "Modify the selection color, it\'s useful for grouping.") language.Add("Undone_smartweld", "Undone Smart-Weld") language.Add("Cleanup_smartweld", "Smart Welds") language.Add("Cleaned_smartweld", "Smart-Welds Cleared") end function TOOL.BuildCPanel(panel) panel:SetName("Smart Weld") panel:AddControl("Header", { Text = "", Description = "Automatically welds selected props." }) -- Outside Radius panel:AddControl("Slider", { Label = "#tool.smartweld.selectoutsideradius", Help = "#tool.smartweld.selectoutsideradius", Type = "float", Min = "0", Max = "1000", Command = "smartweld_selectradius" }) -- Force Limit panel:AddControl("Slider", { Label = "#tool.smartweld.strength", Help = "#tool.smartweld.strength", Type = "float", Min = "0", Max = "10000", Command = "smartweld_strength" }) -- Max Welds Per Prop panel:AddControl("Slider", { Label = "#tool.smartweld.maxweldsperprop", Help = "#tool.smartweld.maxweldsperprop", Type = "Integer", Min = "1", Max = "10", Command = "smartweld_maxweldsperprop" }) -- Weld to each other or all to world panel:AddControl("Checkbox", { Label = "#tool.smartweld.world", Help = "#tool.smartweld.world", Command = "smartweld_world" }) -- No-Collide Props While Welding panel:AddControl("Checkbox", { Label = "#tool.smartweld.nocollide", Help = "#tool.smartweld.nocollide", Command = "smartweld_nocollide" }) -- Freeze Props When Welded panel:AddControl("Checkbox", { Label = "#tool.smartweld.freeze", Help = "#tool.smartweld.freeze", Command = "smartweld_freeze" }) -- Clear Previous Welds Before Welding panel:AddControl("Checkbox", { Label = "#tool.smartweld.clearwelds", Help = "#tool.smartweld.clearwelds", Command = "smartweld_clearwelds" }) -- Color panel:AddControl("Color", { Label = "#tool.smartweld.color", Help = "#tool.smartweld.color", Red = "smartweld_color_r", Green = "smartweld_color_g", Blue = "smartweld_color_b", Alpha = "smartweld_color_a" }) end -- Micro Optimizations! local ipairs = ipairs local IsValid = IsValid local Weld = constraint.Weld local AddEntity = undo.AddEntity local Cleanup = cleanup.Add -- Clears selected props when you die or holster the tool. function TOOL:Holster() if CLIENT or game.SinglePlayer() then for k, v in ipairs(self.SelectedProps) do if IsValid(v.ent) then v.ent:SetColor(v.col) end end end self.SelectedProps = {} self:SetStage(1) end -- Pretty much deselects all function TOOL:Reload() if IsFirstTimePredicted() and self.SelectedProps and #self.SelectedProps > 0 then self:Holster() self:Notify("Prop Selection Cleared", NOTIFY_CLEANUP) end end -- Does some validity checks then either selects or deselects the prop. function TOOL:LeftClick(tr) if IsFirstTimePredicted() and IsValid(tr.Entity) and not tr.Entity:IsPlayer() then if SERVER and not util.IsValidPhysicsObject(tr.Entity, tr.PhysicsBone) then return false end if self:GetOwner():KeyDown(IN_USE) then return self:AutoSelect(tr.Entity) end return self:HandleProp(tr) end return false end -- Auto-selects props function TOOL:AutoSelect(ent) if not IsValid(ent) then return false end local preAutoSelect = #self.SelectedProps local selectRadius = self:GetClientNumber("selectradius") local radiusProps = ents.FindInSphere(ent:GetPos(), selectRadius) if #radiusProps < 1 then return false end local numNearProps = 0 for i = 1, #radiusProps do if self:IsAllowedEnt(ent) and not self:PropHasBeenSelected(radiusProps[i]) then self:SelectProp(radiusProps[i]) numNearProps = numNearProps + 1 end end self:Notify(#self.SelectedProps-preAutoSelect .." prop(s) have been auto-selected.", NOTIFY_GENERIC) end -- Decides if we should select or deselect the specified entity. function TOOL:HandleProp(tr) if #self.SelectedProps == 0 then self:SelectProp(tr.Entity, tr.PhysicsBone) else for k, v in ipairs(self.SelectedProps) do if v.ent == tr.Entity then self:DeselectProp(tr.Entity) return true end end self:SelectProp(tr.Entity, tr.PhysicsBone) end return true end -- Deselects the chosen prop. function TOOL:DeselectProp(ent) for k, v in ipairs(self.SelectedProps) do if v.ent == ent then if CLIENT or game.SinglePlayer() then ent:SetColor(v.col) end table.remove(self.SelectedProps, k) end end return true end -- Adds prop to props table and sets its color. function TOOL:SelectProp(entity, hitBoneNum) if self:IsAllowedEnt(entity) then if #self.SelectedProps == 0 then self:SetStage(2) end local boneNum = entity:IsRagdoll() and hitBoneNum or 0 table.insert(self.SelectedProps, { ent = entity, col = entity:GetColor(), bone = boneNum }) if CLIENT or game.SinglePlayer() then entity:SetColor(Color(self:GetClientNumber("color_r", 0), self:GetClientNumber("color_g", 0), self:GetClientNumber("color_b", 0), self:GetClientNumber("color_a", 255))) end return true end return false end -- Handles the welding function TOOL:RightClick(tr) if #self.SelectedProps <= 1 then self:Notify((#self.SelectedProps == 1 and "Select at least one more prop to weld." or "No props selected!"), NOTIFY_GENERIC) return false end if SERVER then undo.Create("smartweld") self:PreWeld() self:PerformWeld(tr) undo.SetPlayer(self:GetOwner()) undo.Finish() end self:FinishWelding(tr.Entity) return false end -- Does stuff that should happen before welding such as clearing old welds or freezing all the props. function TOOL:PreWeld() local freezeProps = self:GetClientNumber("freeze") local removeOldWelds = self:GetClientNumber("clearwelds") for k, v in ipairs(self.SelectedProps) do if IsValid(v.ent) then if removeOldWelds == 1 then constraint.RemoveConstraints(v.ent, "Weld") end if freezeProps == 1 then local physobj = v.ent:GetPhysicsObject() if IsValid(physobj) then physobj:EnableMotion(false) self:GetOwner():AddFrozenPhysicsObject(v.ent, physobj) end end end end end -- Decides what kind of weld to perform and then does it. function TOOL:PerformWeld(tr) local weldToWorld = tobool(self:GetClientNumber("world")) local nocollide = tobool(self:GetClientNumber("nocollide")) local weldForceLimit = math.floor(self:GetClientNumber("strength")) local ply = self:GetOwner() if #self.SelectedProps < 2 then return end if weldToWorld then local world = game.GetWorld() for _, v in ipairs(self.SelectedProps) do local weld = Weld(v.ent, world, 0, 0, weldForceLimit, nocollide, false) AddEntity(weld) Cleanup(ply, "smartweld", weld) end elseif self:GetOwner():KeyDown(IN_USE) then -- Weld all to one for _, v in ipairs(self.SelectedProps) do local weld = Weld(v.ent, tr.Entity, v.bone, tr.PhysicsBone, weldForceLimit, nocollide, false) AddEntity(weld) Cleanup(ply, "smartweld", weld) end elseif #self.SelectedProps < 128 then for i = 1, #self.SelectedProps do local firstprop = self.SelectedProps[i] for k = i+1, #self.SelectedProps do local secondprop = self.SelectedProps[k] if IsValid(firstprop.ent) and IsValid(secondprop.ent) then local weld = Weld(firstprop.ent, secondprop.ent, firstprop.bone, secondprop.bone, weldForceLimit, nocollide, false) AddEntity(weld) Cleanup(ply, "smartweld", weld) end end end else -- There is a source engine limit with welding more than 127 props so we have to work around it by welding to the closest props. local function AreLinked(prop_one, prop_two) return self.SelectedProps[prop_two][prop_one] == true or self.SelectedProps[prop_one][prop_two] == true end local function LinkProps(id_one, prop_one, id_two) local weld = Weld(prop_one.ent, self.SelectedProps[id_two].ent, 0, 0, weldForceLimit, nocollide, false) AddEntity(weld) Cleanup(ply, "smartweld", weld) -- This kinda makes a mess in the SelectedProps table but we clear it right after this function anyways. self.SelectedProps[id_one][id_two] = true self.SelectedProps[id_two][id_one] = true end local maxweldsperprop = math.min(self:GetClientNumber("maxweldsperprop"), 15) for i, v in ipairs(self.SelectedProps) do self.SelectedProps[i][i] = true for _ = 1, maxweldsperprop do local closestdistance = math.huge local closestprop_id = -1 for j, d in ipairs(self.SelectedProps) do if not AreLinked(i, j) then local distance = (v.ent:GetPos() - d.ent:GetPos()):LengthSqr() if distance < closestdistance then closestdistance = distance closestprop_id = j end end end if closestprop_id ~= -1 then LinkProps(i, v, closestprop_id) end end end end end function TOOL:FinishWelding(entity) if CLIENT or game.SinglePlayer() then local numProps = 0 for k, v in ipairs(self.SelectedProps) do if IsValid(v.ent) then v.ent:SetColor(v.col) numProps = numProps + 1 end end if self:GetOwner():KeyDown(IN_USE) then -- If they chose to weld all to one prop this will correct the count. if not self:PropHasBeenSelected(entity) then numProps = numProps + 1 end self:Notify("Weld complete! ".. numProps .." props have been welded to a single prop.", NOTIFY_GENERIC) elseif tobool(self:GetClientNumber("world")) then self:Notify("Weld complete! ".. numProps .." props have been welded to the world.", NOTIFY_GENERIC) else self:Notify("Weld complete! ".. numProps .." props have been welded to each other.", NOTIFY_GENERIC) end end self.SelectedProps = {} self:SetStage(1) end -- Checks if a prop has already been selected. function TOOL:PropHasBeenSelected(ent) for k, v in ipairs(self.SelectedProps) do if ent == v.ent then return true end end return false end -- Decides if we want to weld the entity or not. function TOOL:IsAllowedEnt(ent) if IsValid(ent) then local ply = SERVER and self:GetOwner() or self.Owner local class = ent:GetClass() local tr = ply:GetEyeTrace() tr.Entity = ent if (not hook.Run("CanTool", ply, tr, "smartweld")) or ((not self.AllowedBaseClasses[ent.Base]) and (not self.AllowedClasses[class])) then return false end return true end return false end -- Puts one of those annoying notifications to the lower right of the screen. function TOOL:Notify(text, notifyType) if IsFirstTimePredicted() then if CLIENT and IsValid(self.Owner) then notification.AddLegacy(text, notifyType, 5) surface.PlaySound("buttons/button15.wav") elseif game.SinglePlayer() then self:GetOwner():SendLua("GAMEMODE:AddNotify(\"".. text .."\", ".. tostring(notifyType) ..", 5)") -- Because singleplayer is doodoo. self:GetOwner():SendLua("surface.PlaySound(\"buttons/button15.wav\")") end end end