--[[ | 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/ --]] language.Add("pac_projectile", "Projectile") local BUILDER, PART = pac.PartTemplate("base_movable") PART.ClassName = "projectile" PART.Group = 'advanced' PART.Icon = 'icon16/bomb.png' BUILDER:StartStorableVars() BUILDER:GetSet("Speed", 1) BUILDER:GetSet("AddOwnerSpeed", false) BUILDER:GetSet("Damping", 0) BUILDER:GetSet("Gravity", true) BUILDER:GetSet("Collisions", true) BUILDER:GetSet("Sphere", false) BUILDER:GetSet("Radius", 1) BUILDER:GetSet("DamageRadius", 50) BUILDER:GetSet("LifeTime", 5) BUILDER:GetSet("AimDir", false) BUILDER:GetSet("Sticky", false) BUILDER:GetSet("Bounce", 0) BUILDER:GetSet("BulletImpact", false) BUILDER:GetSet("Damage", 0) BUILDER:GetSet("DamageType", "generic", {enums = { generic = 0, --generic damage crush = 1, --caused by physics interaction bullet = 2, --bullet damage slash = 4, --sharp objects, such as manhacks or other npcs attacks burn = 8, --damage from fire vehicle = 16, --hit by a vehicle fall = 32, --fall damage blast = 64, --explosion damage club = 128, --crowbar damage shock = 256, --electrical damage, shows smoke at the damage position sonic = 512, --sonic damage,used by the gargantua and houndeye npcs energybeam = 1024, --laser nevergib = 4096, --don't create gibs alwaysgib = 8192, --always create gibs drown = 16384, --drown damage paralyze = 32768, --same as dmg_poison nervegas = 65536, --neurotoxin damage poison = 131072, --poison damage acid = 1048576, -- airboat = 33554432, --airboat gun damage blast_surface = 134217728, --this won't hurt the player underwater buckshot = 536870912, --the pellets fired from a shotgun direct = 268435456, -- dissolve = 67108864, --forces the entity to dissolve on death drownrecover = 524288, --damage applied to the player to restore health after drowning physgun = 8388608, --damage done by the gravity gun plasma = 16777216, -- prevent_physics_force = 2048, -- radiation = 262144, --radiation removenoragdoll = 4194304, --don't create a ragdoll on death slowburn = 2097152, -- explosion = -1, -- util.BlastDamage fire = -1, -- ent:Ignite(5) -- env_entity_dissolver dissolve_energy = 0, dissolve_heavy_electrical = 1, dissolve_light_electrical = 2, dissolve_core_effect = 3, heal = -1, armor = -1, } }) BUILDER:GetSet("Spread", 0) BUILDER:GetSet("Delay", 0) BUILDER:GetSet("Maximum", 0) BUILDER:GetSet("Mass", 100) BUILDER:GetSet("Attract", 0) BUILDER:GetSet("AttractMode", "projectile_nearest", {enums = { hitpos = "hitpos", hitpos_radius = "hitpos_radius", closest_to_projectile = "closest_to_projectile", closest_to_hitpos = "closest_to_hitpos", }}) BUILDER:GetSet("AttractRadius", 200) BUILDER:GetSetPart("OutfitPart") BUILDER:GetSet("Physical", false) BUILDER:GetSet("CollideWithOwner", false) BUILDER:GetSet("CollideWithSelf", false) BUILDER:GetSet("RemoveOnCollide", false) BUILDER:EndStorableVars() PART.Translucent = false function PART:OnShow(from_rendering) if not from_rendering then -- TODO: -- this makes sure all the parents of this movable have an up-to-date draw position -- GetBonePosition implicitly uses ent:GetPos() as the parent origin which is really bad, -- it should instead be using what pac considers to be the position --self:GetRootPart():CallRecursive("Draw", "opaque") local parents = self:GetParentList() -- call draw from root to the current part only on direct parents to update the position hiearchy for i = #parents, 1, -1 do local part = parents[i] if part.Draw then part:Draw("opaque") end end self:Shoot(self:GetDrawPosition()) end end function PART:AttachToEntity(ent) if not self.OutfitPart:IsValid() then return false end ent.pac_draw_distance = 0 local tbl = self.OutfitPart:ToTable() local group = pac.CreatePart("group", self:GetPlayerOwner()) group:SetShowInEditor(false) local part = pac.CreatePart(tbl.self.ClassName, self:GetPlayerOwner(), tbl, tostring(tbl)) group:AddChild(part) group:SetOwner(ent) group.SetOwner = function(s) s.Owner = ent end part:SetHide(false) local id = group.Id local owner_id = self:GetPlayerOwnerId() if owner_id then id = id .. owner_id end ent:CallOnRemove("pac_projectile_" .. id, function() group:Remove() end) group:CallRecursive("Think") ent.RenderOverride = ent.RenderOverride or function() if self.AimDir then ent:SetRenderAngles(ent:GetVelocity():Angle()) end end ent.pac_projectile_part = group ent.pac_projectile = self return true end local enable = CreateClientConVar("pac_sv_projectiles", 0, true) function PART:Shoot(pos, ang) local physics = self.Physical if physics then if pac.LocalPlayer ~= self:GetPlayerOwner() then return end local tbl = {} for key in pairs(self:GetStorableVars()) do tbl[key] = self[key] end net.Start("pac_projectile") net.WriteVector(pos) net.WriteAngle(ang) net.WriteTable(tbl) net.SendToServer() else self.projectiles = self.projectiles or {} local count = 0 for key, ent in pairs(self.projectiles) do if not ent:IsValid() then self.projectiles[key] = nil else count = count + 1 end end local max = math.min(self.Maximum, 100) if max == 0 then max = 100 end if count > max then return end local function spawn() if not self:IsValid() then return end local ent = pac.CreateEntity("models/props_junk/popcan01a.mdl") if not ent:IsValid() then return end local idx = table.insert(self.projectiles, ent) ent:AddCallback("PhysicsCollide", function(ent, data) local phys = ent:GetPhysicsObject() if self.Bounce > 0 then timer.Simple(0, function() if phys:IsValid() then phys:SetVelocity(data.OurOldVelocity - 2 * (data.HitNormal:Dot(data.OurOldVelocity) * data.HitNormal) * self.Bounce) end end) elseif self.Sticky then phys:SetVelocity(Vector(0,0,0)) phys:EnableMotion(false) ent.pac_stuck = data.OurOldVelocity end if self.BulletImpact then ent:FireBullets{ Attacker = ent:GetOwner(), Damage = 0, Force = 0, Num = 1, Src = data.HitPos - data.HitNormal, Dir = data.HitNormal, Distance = 10, } end if self.RemoveOnCollide then timer.Simple(0.01, function() SafeRemoveEntity(ent) end) end end) ent:SetOwner(self:GetPlayerOwner(true)) ent:SetPos(pos) ent:SetAngles(ang) ent:SetCollisionGroup(COLLISION_GROUP_PROJECTILE) if self.Sphere then ent:PhysicsInitSphere(math.Clamp(self.Radius, 1, 30)) else ent:PhysicsInitBox(Vector(1,1,1) * - math.Clamp(self.Radius, 1, 30), Vector(1,1,1) * math.Clamp(self.Radius, 1, 30)) end ent.RenderOverride = function() if not self:IsValid() then return end if not self:GetRootPart():GetOwner():IsValid() then timer.Simple(0, function() SafeRemoveEntity(ent) end) end if self.AimDir then if ent.pac_stuck then ent:SetRenderAngles(ent.pac_stuck:Angle()) else local angle = ent:GetVelocity():Angle() ent:SetRenderAngles(angle) ent.last_angle = angle end end end local phys = ent:GetPhysicsObject() phys:EnableGravity(self.Gravity) phys:AddVelocity((ang:Forward() + (VectorRand():Angle():Forward() * self.Spread)) * self.Speed * 1000) if self.AddOwnerSpeed and ent:GetOwner():IsValid() then phys:AddVelocity(ent:GetOwner():GetVelocity()) end phys:EnableCollisions(self.Collisions) phys:SetDamping(self.Damping, 0) ent:SetCollisionGroup(COLLISION_GROUP_PROJECTILE) if self:AttachToEntity(ent) then timer.Simple(math.Clamp(self.LifeTime, 0, 10), function() if ent:IsValid() then if ent.pac_projectile_part and ent.pac_projectile_part:IsValid() then ent.pac_projectile_part:Remove() end timer.Simple(0.5, function() SafeRemoveEntity(ent) end) end end) end end if self.Delay == 0 then spawn() else timer.Simple(self.Delay, spawn) end end end function PART:OnRemove() if not self.Physical and self.projectiles then for key, ent in pairs(self.projectiles) do SafeRemoveEntity(ent) end self.projectiles = {} end end --[[ function PART:OnHide() if self.RemoveOnHide then self:OnRemove() end end ]] do -- physical local Entity = Entity local projectiles = {} pac.AddHook("Think", "pac_projectile", function() for key, data in pairs(projectiles) do if not data.ply:IsValid() then projectiles[key] = nil goto CONTINUE end local ent = Entity(data.ent_id) if ent:IsValid() and ent:GetClass() == "pac_projectile" then local part = pac.GetPartFromUniqueID(pac.Hash(data.ply), data.partuid) if part:IsValid() and part:GetPlayerOwner() == data.ply then part:AttachToEntity(ent) end projectiles[key] = nil end ::CONTINUE:: end end) net.Receive("pac_projectile_attach", function() local ply = net.ReadEntity() local ent_id = net.ReadInt(16) local partuid = net.ReadString() if ply:IsValid() then table.insert(projectiles, {ply = ply, ent_id = ent_id, partuid = partuid}) end end) end BUILDER:Register()