--[[ | 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/ --]] ---- Carry weapon SWEP AddCSLuaFile() DEFINE_BASECLASS "weapon_tttbase" SWEP.HoldType = "pistol" if CLIENT then SWEP.PrintName = "magnet_name" SWEP.Slot = 4 SWEP.DrawCrosshair = false SWEP.ViewModelFlip = false end SWEP.Base = "weapon_tttbase" SWEP.AutoSpawnable = false SWEP.ViewModel = Model("models/weapons/v_stunbaton.mdl") SWEP.WorldModel = Model("models/weapons/w_stunbaton.mdl") SWEP.Primary.ClipSize = -1 SWEP.Primary.DefaultClip = -1 SWEP.Primary.Automatic = true SWEP.Primary.Ammo = "none" SWEP.Primary.Delay = 0.1 SWEP.Secondary.ClipSize = -1 SWEP.Secondary.DefaultClip = -1 SWEP.Secondary.Automatic = true SWEP.Secondary.Ammo = "none" SWEP.Secondary.Delay = 0.1 SWEP.Kind = WEAPON_CARRY SWEP.InLoadoutFor = {ROLE_INNOCENT, ROLE_TRAITOR, ROLE_DETECTIVE} SWEP.AllowDelete = false SWEP.AllowDrop = false SWEP.NoSights = true SWEP.EntHolding = nil SWEP.CarryHack = nil SWEP.Constr = nil SWEP.PrevOwner = nil local allow_rag = CreateConVar("ttt_ragdoll_carrying", "1") local prop_force = CreateConVar("ttt_prop_carrying_force", "60000") local no_throw = CreateConVar("ttt_no_prop_throwing", "0") local pin_rag = CreateConVar("ttt_ragdoll_pinning", "1") local pin_rag_inno = CreateConVar("ttt_ragdoll_pinning_innocents", "0") -- Allowing weapon pickups can allow players to cause a crash in the physics -- system (ie. not fixable). Tuning the range seems to make this more -- difficult. Not sure why. It's that kind of crash. local allow_wep = CreateConVar("ttt_weapon_carrying", "0") local wep_range = CreateConVar("ttt_weapon_carrying_range", "50") -- not customizable via convars as some objects rely on not being carryable for -- gameplay purposes CARRY_WEIGHT_LIMIT = 45 local PIN_RAG_RANGE = 90 local player = player local IsValid = IsValid local CurTime = CurTime local function SetSubPhysMotionEnabled(ent, enable) if not IsValid(ent) then return end for i=0, ent:GetPhysicsObjectCount()-1 do local subphys = ent:GetPhysicsObjectNum(i) if IsValid(subphys) then subphys:EnableMotion(enable) if enable then subphys:Wake() end end end end local function KillVelocity(ent) ent:SetVelocity(vector_origin) -- The only truly effective way to prevent all kinds of velocity and -- inertia is motion disabling the entire ragdoll for a tick -- for non-ragdolls this will do the same for their single physobj SetSubPhysMotionEnabled(ent, false) timer.Simple(0, function() SetSubPhysMotionEnabled(ent, true) end) end function SWEP:Reset(keep_velocity) if IsValid(self.CarryHack) then self.CarryHack:Remove() end if IsValid(self.Constr) then self.Constr:Remove() end if IsValid(self.EntHolding) then -- it is possible for weapons to be already equipped at this point -- changing the owner in such a case would cause problems if not self.EntHolding:IsWeapon() then if not IsValid(self.PrevOwner) then self.EntHolding:SetOwner(nil) else self.EntHolding:SetOwner(self.PrevOwner) end end -- the below ought to be unified with self:Drop() local phys = self.EntHolding:GetPhysicsObject() if IsValid(phys) then phys:ClearGameFlag(FVPHYSICS_PLAYER_HELD) phys:AddGameFlag(FVPHYSICS_WAS_THROWN) phys:EnableCollisions(true) phys:EnableGravity(true) phys:EnableDrag(true) phys:EnableMotion(true) end if (not keep_velocity) and (no_throw:GetBool() or self.EntHolding:GetClass() == "prop_ragdoll") then KillVelocity(self.EntHolding) end end self.dt.carried_rag = nil self.EntHolding = nil self.CarryHack = nil self.Constr = nil end SWEP.reset = SWEP.Reset function SWEP:CheckValidity() if (not IsValid(self.EntHolding)) or (not IsValid(self.CarryHack)) or (not IsValid(self.Constr)) then -- if one of them is not valid but another is non-nil... if (self.EntHolding or self.CarryHack or self.Constr) then self:Reset() end return false else return true end end local function PlayerStandsOn(ent) for _, ply in player.Iterator() do if ply:GetGroundEntity() == ent and ply:IsTerror() then return true end end return false end if SERVER then local ent_diff = vector_origin local ent_diff_time = CurTime() local stand_time = 0 function SWEP:Think() BaseClass.Think(self) if not self:CheckValidity() then return end -- If we are too far from our object, force a drop. To avoid doing this -- vector math extremely often (esp. when everyone is carrying something) -- even though the occurrence is very rare, limited to once per -- second. This should be plenty to catch the rare glitcher. if CurTime() > ent_diff_time then ent_diff = self:GetPos() - self.EntHolding:GetPos() if ent_diff:Dot(ent_diff) > 40000 then self:Reset() return end ent_diff_time = CurTime() + 1 end if CurTime() > stand_time then if PlayerStandsOn(self.EntHolding) then self:Reset() return end stand_time = CurTime() + 0.1 end self.CarryHack:SetPos(self:GetOwner():EyePos() + self:GetOwner():GetAimVector() * 70) self.CarryHack:SetAngles(self:GetOwner():GetAngles()) self.EntHolding:PhysWake() end end function SWEP:PrimaryAttack() self:DoAttack(false) end function SWEP:SecondaryAttack() self:DoAttack(true) end function SWEP:MoveObject(phys, pdir, maxforce, is_ragdoll) if not IsValid(phys) then return end local speed = phys:GetVelocity():Length() -- remap speed from 0 -> 125 to force 1 -> 4000 local force = maxforce + (1 - maxforce) * (speed / 125) if is_ragdoll then force = force * 2 end pdir = pdir * force local mass = phys:GetMass() -- scale more for light objects if mass < 50 then pdir = pdir * (mass + 0.5) * (1 / 50) end phys:ApplyForceCenter(pdir) end function SWEP:GetRange(target) if IsValid(target) and target:IsWeapon() and allow_wep:GetBool() then return wep_range:GetFloat() elseif IsValid(target) and target:GetClass() == "prop_ragdoll" then return 75 else return 100 end end function SWEP:AllowPickup(target) local phys = target:GetPhysicsObject() local ply = self:GetOwner() return (IsValid(phys) and IsValid(ply) and (not phys:HasGameFlag(FVPHYSICS_NO_PLAYER_PICKUP)) and phys:GetMass() < CARRY_WEIGHT_LIMIT and (not PlayerStandsOn(target)) and (target.CanPickup != false) and (target:GetClass() != "prop_ragdoll" or allow_rag:GetBool()) and ((not target:IsWeapon()) or allow_wep:GetBool())) end function SWEP:DoAttack(pickup) self:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) self:SetNextSecondaryFire( CurTime() + self.Secondary.Delay ) if IsValid(self.EntHolding) then self:SendWeaponAnim( ACT_VM_MISSCENTER ) if (not pickup) and self.EntHolding:GetClass() == "prop_ragdoll" then -- see if we can pin this ragdoll to a wall in front of us if not self:PinRagdoll() then -- else just drop it as usual self:Drop() end else self:Drop() end self:SetNextSecondaryFire(CurTime() + 0.3) return end local ply = self:GetOwner() local trace = ply:GetEyeTrace(MASK_SHOT) if IsValid(trace.Entity) then local ent = trace.Entity local phys = trace.Entity:GetPhysicsObject() if not IsValid(phys) or not phys:IsMoveable() or phys:HasGameFlag(FVPHYSICS_PLAYER_HELD) then return end -- if we let the client mess with physics, desync ensues if CLIENT then return end if pickup then if (ply:EyePos() - trace.HitPos):Length() < self:GetRange(ent) then if self:AllowPickup(ent) then self:Pickup() self:SendWeaponAnim( ACT_VM_HITCENTER ) -- make the refire slower to avoid immediately dropping local delay = (ent:GetClass() == "prop_ragdoll") and 0.8 or 0.5 self:SetNextSecondaryFire(CurTime() + delay) return else local is_ragdoll = trace.Entity:GetClass() == "prop_ragdoll" -- pull heavy stuff local ent = trace.Entity local phys = ent:GetPhysicsObject() local pdir = trace.Normal * -1 if is_ragdoll then phys = ent:GetPhysicsObjectNum(trace.PhysicsBone) -- increase refire to make rags easier to drag --self:SetNextSecondaryFire(CurTime() + 0.04) end if IsValid(phys) then self:MoveObject(phys, pdir, 6000, is_ragdoll) return end end end else if (ply:EyePos() - trace.HitPos):Length() < 100 then local phys = trace.Entity:GetPhysicsObject() if IsValid(phys) then if IsValid(phys) then local pdir = trace.Normal self:MoveObject(phys, pdir, 6000, (trace.Entity:GetClass() == "prop_ragdoll")) self:SetNextPrimaryFire(CurTime() + 0.03) end end end end end end -- Perform a pickup function SWEP:Pickup() if CLIENT or IsValid(self.EntHolding) then return end local ply = self:GetOwner() local trace = ply:GetEyeTrace(MASK_SHOT) local ent = trace.Entity self.EntHolding = ent local entphys = ent:GetPhysicsObject() if IsValid(ent) and IsValid(entphys) then self.CarryHack = ents.Create("prop_physics") if IsValid(self.CarryHack) then self.CarryHack:SetPos(self.EntHolding:GetPos()) self.CarryHack:SetModel("models/weapons/w_bugbait.mdl") self.CarryHack:SetColor(Color(50, 250, 50, 240)) self.CarryHack:SetNoDraw(true) self.CarryHack:DrawShadow(false) self.CarryHack:SetHealth(999) self.CarryHack:SetOwner(ply) self.CarryHack:SetCollisionGroup(COLLISION_GROUP_DEBRIS) self.CarryHack:SetSolid(SOLID_NONE) -- set the desired angles before adding the constraint self.CarryHack:SetAngles(self:GetOwner():GetAngles()) self.CarryHack:Spawn() -- if we already are owner before pickup, we will not want to disown -- this entity when we drop it -- weapons should not have their owner changed in this way if not self.EntHolding:IsWeapon() then self.PrevOwner = self.EntHolding:GetOwner() self.EntHolding:SetOwner(ply) end local phys = self.CarryHack:GetPhysicsObject() if IsValid(phys) then phys:SetMass(200) phys:SetDamping(0, 1000) phys:EnableGravity(false) phys:EnableCollisions(false) phys:EnableMotion(false) phys:AddGameFlag(FVPHYSICS_PLAYER_HELD) end entphys:AddGameFlag(FVPHYSICS_PLAYER_HELD) local bone = math.Clamp(trace.PhysicsBone, 0, 1) local max_force = prop_force:GetInt() if ent:GetClass() == "prop_ragdoll" then self.dt.carried_rag = ent bone = trace.PhysicsBone max_force = 0 else self.dt.carried_rag = nil end self.Constr = constraint.Weld(self.CarryHack, self.EntHolding, 0, bone, max_force, true) end end end local down = Vector(0, 0, -1) function SWEP:AllowEntityDrop() local ply = self:GetOwner() local ent = self.CarryHack if (not IsValid(ply)) or (not IsValid(ent)) then return false end local ground = ply:GetGroundEntity() if ground and (ground:IsWorld() or IsValid(ground)) then return true end local diff = (ent:GetPos() - ply:GetShootPos()):GetNormalized() return down:Dot(diff) <= 0.75 end function SWEP:Drop() if not self:CheckValidity() then return end if not self:AllowEntityDrop() then return end if SERVER then self.Constr:Remove() self.CarryHack:Remove() local ent = self.EntHolding local phys = ent:GetPhysicsObject() if IsValid(phys) then phys:EnableCollisions(true) phys:EnableGravity(true) phys:EnableDrag(true) phys:EnableMotion(true) phys:Wake() phys:ApplyForceCenter(self:GetOwner():GetAimVector() * 500) phys:ClearGameFlag(FVPHYSICS_PLAYER_HELD) phys:AddGameFlag(FVPHYSICS_WAS_THROWN) end -- Try to limit ragdoll slinging if no_throw:GetBool() or ent:GetClass() == "prop_ragdoll" then KillVelocity(ent) end ent:SetPhysicsAttacker(self:GetOwner()) end self:Reset() end local CONSTRAINT_TYPE = "Rope" local function RagdollPinnedTakeDamage(rag, dmginfo) local att = dmginfo:GetAttacker() if not IsValid(att) then return end -- drop from pinned position upon dmg constraint.RemoveConstraints(rag, CONSTRAINT_TYPE) rag:PhysWake() rag:SetHealth(0) rag.is_pinned = false end function SWEP:PinRagdoll() if not pin_rag:GetBool() then return end if (not self:GetOwner():IsTraitor()) and (not pin_rag_inno:GetBool()) then return end local rag = self.EntHolding local ply = self:GetOwner() local tr = util.TraceLine({start = ply:EyePos(), endpos = ply:EyePos() + (ply:GetAimVector() * PIN_RAG_RANGE), filter = {ply, self, rag, self.CarryHack}, mask = MASK_SOLID}) if tr.HitWorld and (not tr.HitSky) then -- find bone we're holding the ragdoll by local bone = self.Constr.Bone2 -- only allow one rope per bone for _, c in pairs(constraint.FindConstraints(rag, CONSTRAINT_TYPE)) do if c.Bone1 == bone then c.Constraint:Remove() end end local bonephys = rag:GetPhysicsObjectNum(bone) if not IsValid(bonephys) then return end local bonepos = bonephys:GetPos() local attachpos = tr.HitPos local length = (bonepos - attachpos):Length() * 0.9 -- we need to convert using this particular physobj to get the right -- coordinates bonepos = bonephys:WorldToLocal(bonepos) constraint.Rope(rag, tr.Entity, bone, 0, bonepos, attachpos, length, length * 0.1, 6000, 1, "cable/rope", false) rag.is_pinned = true rag.OnPinnedDamage = RagdollPinnedTakeDamage -- lets EntityTakeDamage run for the ragdoll rag:SetHealth(999999) self:Reset(true) end end function SWEP:SetupDataTables() -- we've got these dt slots anyway, might as well use them instead of a -- globalvar, probably cheaper self:DTVar("Bool", 0, "can_rag_pin") self:DTVar("Bool", 1, "can_rag_pin_inno") -- client actually has no idea what we're holding, and almost never needs to -- know self:DTVar("Entity", 0, "carried_rag") return self.BaseClass.SetupDataTables(self) end if SERVER then function SWEP:Initialize() self.dt.can_rag_pin = pin_rag:GetBool() self.dt.can_rag_pin_inno = pin_rag_inno:GetBool() self.dt.carried_rag = nil return self.BaseClass.Initialize(self) end end function SWEP:OnRemove() self:Reset() end function SWEP:Deploy() self:Reset() return true end function SWEP:Holster() self:Reset() return true end function SWEP:ShouldDropOnDie() return false end function SWEP:OnDrop() self:Remove() end if CLIENT then local draw = draw local util = util local PT = LANG.GetParamTranslation local key_params = {primaryfire = Key("+attack", "LEFT MOUSE")} function SWEP:DrawHUD() self.BaseClass.DrawHUD(self) if self.dt.can_rag_pin and IsValid(self.dt.carried_rag) then local client = LocalPlayer() if not client:IsSpec() and (self.dt.can_rag_pin_inno or client:IsTraitor()) then local tr = util.TraceLine({start = client:EyePos(), endpos = client:EyePos() + (client:GetAimVector() * PIN_RAG_RANGE), filter = {client, self, self.dt.carried_rag}, mask = MASK_SOLID}) if tr.HitWorld and (not tr.HitSky) then draw.SimpleText(PT("magnet_help", key_params), "TabLarge", ScrW() / 2, ScrH() / 2 - 50, COLOR_RED, TEXT_ALIGN_CENTER) end end end end end