add sborka

This commit is contained in:
2026-03-31 10:27:04 +03:00
commit f5e5f56c84
2345 changed files with 382127 additions and 0 deletions

View File

@@ -0,0 +1,618 @@
AddCSLuaFile()
ENT.Type = "anim"
ENT.Base = "base_entity"
ENT.RenderGroup = RENDERGROUP_TRANSLUCENT
ENT.PrintName = "Base Projectile"
ENT.Category = ""
ENT.Spawnable = false
ENT.Model = ""
local smokeimages = {"particle/smokesprites_0001", "particle/smokesprites_0002", "particle/smokesprites_0003", "particle/smokesprites_0004", "particle/smokesprites_0005", "particle/smokesprites_0006", "particle/smokesprites_0007", "particle/smokesprites_0008", "particle/smokesprites_0009", "particle/smokesprites_0010", "particle/smokesprites_0011", "particle/smokesprites_0012", "particle/smokesprites_0013", "particle/smokesprites_0014", "particle/smokesprites_0015", "particle/smokesprites_0016"}
local function GetSmokeImage()
return smokeimages[math.random(#smokeimages)]
end
ENT.Material = false // custom material
ENT.IsRocket = false // projectile has a booster and will not drop.
ENT.Sticky = false // projectile sticks on impact
ENT.InstantFuse = true // projectile is armed immediately after firing.
ENT.TimeFuse = false // projectile will arm after this amount of time
ENT.RemoteFuse = false // allow this projectile to be triggered by remote detonator.
ENT.ImpactFuse = false // projectile explodes on impact.
ENT.StickyFuse = false // projectile becomes timed after sticking.
ENT.SafetyFuse = 0 // impact fuse hitting too early will not detonate
ENT.RemoveOnImpact = false
ENT.ExplodeOnImpact = false
ENT.ExplodeOnDamage = false // projectile explodes when it takes damage.
ENT.ExplodeUnderwater = false // projectile explodes when it enters water
ENT.Defusable = false // press E on the projectile to defuse it
ENT.DefuseOnDamage = false
ENT.ImpactDamage = 50
ENT.ImpactDamageType = DMG_CRUSH + DMG_CLUB
ENT.Delay = 0 // after being triggered and this amount of time has passed, the projectile will explode.
ENT.SoundHint = false // Emit a sound hint so NPCs can react
ENT.SoundHintDelay = 0 // Delay after spawn
ENT.SoundHintRadius = 328
ENT.SoundHintDuration = 1
ENT.Armed = false
ENT.SmokeTrail = false // leaves trail of smoke
ENT.FlareColor = nil
ENT.FlareSizeMin = 200
ENT.FlareSizeMax = 250
// Guided projectile related
ENT.SteerDelay = 0 // Delay before steering logic kicks in
ENT.SteerSpeed = 60 // Turn rate in degrees per second
ENT.SteerBrake = 0 // Amount of speed to slow down by when turning
ENT.SeekerAngle = 180 // Angle difference (degrees) above which projectile loses target
ENT.SeekerExplodeRange = 256 // Distance to the target below which the missile will immediately explode
ENT.SeekerExplodeSnapPosition = true // When exploding on a seeked target, teleport to the entity's position for more damage
ENT.SeekerExplodeAngle = 180 // Angle tolerance (degrees) below which detonation can happen
ENT.TopAttack = false // This missile will attack from the top
ENT.TopAttackHeight = 512 // Distance above target to top attack
ENT.TopAttackDistance = 128 // Distance from target to stop top attacking
ENT.RocketLifetime = 30 // Rocket will cut after this time
ENT.MinSpeed = 0
ENT.MaxSpeed = 0
ENT.Acceleration = 0
ENT.LeadTarget = false // account for target's velocity and distance
ENT.SuperSteerTime = 0 // Amount of time where turn rate is boosted
ENT.SuperSteerSpeed = 100 // Boosted turn rate in degrees per seconds
ENT.NoReacquire = true // If target is lost, it cannot be tracked anymore
ENT.FlareRedirectChance = 0
ENT.LockOnEntity = NULL
ENT.TargetPos = nil
ENT.AudioLoop = nil
ENT.BounceSounds = nil
ENT.CollisionSphere = nil
ENT.GunshipWorkaround = true
// Tell LVS to not ricochet us
ENT.DisableBallistics = true
ENT.TopAttacking = false
ENT.StartSuperSteerTime = 0
function ENT:SetupDataTables()
self:NetworkVar("Entity", 0, "Weapon")
end
function ENT:Initialize()
if SERVER then
self:SetModel(self.Model)
self:SetMaterial(self.Material or "")
if self.CollisionSphere then
self:PhysicsInitSphere(self.CollisionSphere)
else
self:PhysicsInit(SOLID_VPHYSICS)
end
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:SetCollisionGroup(COLLISION_GROUP_PROJECTILE)
if self.Defusable then
self:SetUseType(SIMPLE_USE)
end
local phys = self:GetPhysicsObject()
if !phys:IsValid() then
self:Remove()
return
end
phys:EnableDrag(false)
phys:SetDragCoefficient(0)
phys:SetBuoyancyRatio(0)
phys:Wake()
if self.IsRocket then
phys:EnableGravity(false)
end
if self.TopAttack then
self.TopAttacking = true
end
self:SwitchTarget(self.LockOnEntity)
end
self.StartSuperSteerTime = CurTime()
self.SpawnTime = CurTime()
self.NextFlareRedirectTime = 0
self.NPCDamage = IsValid(self:GetOwner()) and self:GetOwner():IsNPC() and !TacRP.ConVars["npc_equality"]:GetBool()
if self.AudioLoop then
self.LoopSound = CreateSound(self, self.AudioLoop)
self.LoopSound:Play()
end
if self.InstantFuse then
self.ArmTime = CurTime()
self.Armed = true
end
self:OnInitialize()
end
function ENT:OnRemove()
if self.LoopSound then
self.LoopSound:Stop()
end
end
function ENT:OnTakeDamage(dmg)
if self.Detonated then return end
// self:TakePhysicsDamage(dmg)
if self.ExplodeOnDamage then
if IsValid(self:GetOwner()) and IsValid(dmg:GetAttacker()) then self:SetOwner(dmg:GetAttacker())
else self.Attacker = dmg:GetAttacker() or self.Attacker end
self:PreDetonate()
elseif self.DefuseOnDamage and dmg:GetDamageType() != DMG_BLAST then
self:EmitSound("physics/plastic/plastic_box_break" .. math.random(1, 2) .. ".wav", 70, math.Rand(95, 105))
local fx = EffectData()
fx:SetOrigin(self:GetPos())
fx:SetNormal(self:GetAngles():Forward())
fx:SetAngles(self:GetAngles())
util.Effect("ManhackSparks", fx)
self.Detonated = true
self:Remove()
end
end
function ENT:PhysicsCollide(data, collider)
if IsValid(data.HitEntity) and data.HitEntity:GetClass() == "func_breakable_surf" then
self:FireBullets({
Attacker = self:GetOwner(),
Inflictor = self,
Damage = 0,
Distance = 32,
Tracer = 0,
Src = self:GetPos(),
Dir = data.OurOldVelocity:GetNormalized(),
})
local pos, ang, vel = self:GetPos(), self:GetAngles(), data.OurOldVelocity
self:SetAngles(ang)
self:SetPos(pos)
self:GetPhysicsObject():SetVelocityInstantaneous(vel * 0.5)
return
end
if (self.SafetyFuse or 0) > 0 and self.SpawnTime + self.SafetyFuse > CurTime() then
self:SafetyImpact(data, collider)
self:Remove()
return
elseif self.ImpactFuse then
if !self.Armed then
self.ArmTime = CurTime()
self.Armed = true
if self:Impact(data, collider) then
return
end
end
if self.Delay == 0 or self.ExplodeOnImpact then
self:PreDetonate(data.HitEntity)
end
elseif self.ImpactDamage > 0 and (self.NextImpactDamage or 0) < CurTime() and data.Speed >= 10 and IsValid(data.HitEntity) and (engine.ActiveGamemode() != "terrortown" or !data.HitEntity:IsPlayer()) then
local dmg = DamageInfo()
dmg:SetAttacker(IsValid(self:GetOwner()) and self:GetOwner() or self.Attacker)
dmg:SetInflictor(self)
dmg:SetDamage(self.ImpactDamage)
dmg:SetDamageType(self.ImpactDamageType)
dmg:SetDamageForce(data.OurOldVelocity)
dmg:SetDamagePosition(data.HitPos)
data.HitEntity:TakeDamageInfo(dmg)
self.NextImpactDamage = CurTime() + 0.05
elseif !self.ImpactFuse then
self:Impact(data, collider)
end
if self.Sticky then
self:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
self:SetPos(data.HitPos)
self:SetAngles((-data.HitNormal):Angle())
if data.HitEntity:IsWorld() or data.HitEntity:GetSolid() == SOLID_BSP then
self:SetMoveType(MOVETYPE_NONE)
self:SetPos(data.HitPos)
else
self:SetPos(data.HitPos)
self:SetParent(data.HitEntity)
end
self:EmitSound("TacRP/weapons/plant_bomb.wav", 65)
self.Attacker = self:GetOwner()
self:SetOwner(NULL)
if self.StickyFuse and !self.Armed then
self.ArmTime = CurTime()
self.Armed = true
end
self:Stuck()
else
if !self.Bounced then
self.Bounced = true
local dot = data.HitNormal:Dot(Vector(0, 0, 1))
if dot < 0 then
self:GetPhysicsObject():SetVelocityInstantaneous(data.OurNewVelocity * (1 + dot * 0.5))
end
end
end
if data.DeltaTime < 0.1 then return end
if !self.BounceSounds then return end
local s = table.Random(self.BounceSounds)
self:EmitSound(s)
end
function ENT:OnThink()
end
function ENT:OnInitialize()
end
function ENT:DoSmokeTrail()
if CLIENT and self.SmokeTrail then
local emitter = ParticleEmitter(self:GetPos())
local smoke = emitter:Add(GetSmokeImage(), self:GetPos())
smoke:SetStartAlpha(50)
smoke:SetEndAlpha(0)
smoke:SetStartSize(10)
smoke:SetEndSize(math.Rand(50, 75))
smoke:SetRoll(math.Rand(-180, 180))
smoke:SetRollDelta(math.Rand(-1, 1))
smoke:SetPos(self:GetPos())
smoke:SetVelocity(-self:GetAngles():Forward() * 400 + (VectorRand() * 10))
smoke:SetColor(200, 200, 200)
smoke:SetLighting(true)
smoke:SetDieTime(math.Rand(0.75, 1.25))
smoke:SetGravity(Vector(0, 0, 0))
emitter:Finish()
end
end
function ENT:PhysicsUpdate(phys)
if self.SpawnTime + self.RocketLifetime < CurTime() then return end
if self.TargetPos and (self.SteerDelay + self.SpawnTime) <= CurTime() then
local v = phys:GetVelocity()
local steer_amount = self.SteerSpeed * FrameTime()
if self.SuperSteerTime + self.StartSuperSteerTime > CurTime() then
steer_amount = self.SuperSteerSpeed * FrameTime()
end
local dir = (self.TargetPos - self:GetPos()):GetNormalized()
local diff = math.deg(math.acos(dir:Dot(self:GetForward())))
local turn_deg = math.min(diff, steer_amount)
local newang = self:GetAngles()
newang:RotateAroundAxis(dir:Cross(self:GetForward()), -turn_deg)
local brake = turn_deg / steer_amount * self.SteerBrake
self:SetAngles(Angle(newang.p, newang.y, 0))
phys:SetVelocityInstantaneous(self:GetForward() * math.Clamp(v:Length() + (self.Acceleration - brake) * FrameTime(), self.MinSpeed, self.MaxSpeed))
elseif self.Acceleration > 0 then
phys:SetVelocityInstantaneous(self:GetForward() * math.Clamp(phys:GetVelocity():Length() + self.Acceleration * FrameTime(), self.MinSpeed, self.MaxSpeed))
end
end
local gunship = {["npc_combinegunship"] = true, ["npc_combinedropship"] = true, ["npc_helicopter"] = true}
function ENT:DoTracking()
local target = self.LockOnEntity
if IsValid(target) then
if self.TopAttack and self.TopAttacking then
local xyvector = (target:WorldSpaceCenter() - self:GetPos())
xyvector.z = 0
local dist = xyvector:Length()
self.TargetPos = target:WorldSpaceCenter() + Vector(0, 0, self.TopAttackHeight)
if self.LeadTarget then
local dist2 = (self.TargetPos - self:GetPos()):Length()
local time = dist2 / self:GetVelocity():Length()
self.TargetPos = self.TargetPos + (target:GetVelocity() * time)
end
if dist <= self.TopAttackDistance then
self.TopAttacking = false
self.StartSuperSteerTime = CurTime()
end
else
local dir = (target:WorldSpaceCenter() - self:GetPos()):GetNormalized()
local diff = math.deg(math.acos(dir:Dot(self:GetForward())))
if diff <= self.SeekerAngle or self.SuperSteerTime + self.StartSuperSteerTime > CurTime() then
self.TargetPos = target:WorldSpaceCenter()
local dist = (self.TargetPos - self:GetPos()):Length()
if self.LeadTarget then
local time = dist / self:GetVelocity():Length()
self.TargetPos = self.TargetPos + (target:GetVelocity() * time)
end
if self.FlareRedirectChance > 0 and self.NextFlareRedirectTime <= CurTime() and !TacRP.FlareEntities[target:GetClass()] then
local flares = ents.FindInSphere(self:GetPos(), 2048)
for k, v in pairs(flares) do
if (TacRP.FlareEntities[v:GetClass()] or v.IsTacRPFlare) and math.Rand(0, 1) <= self.FlareRedirectChance then
self:SwitchTarget(v)
break
end
end
self.NextFlareRedirectTime = CurTime() + 0.5
end
if self.SeekerExplodeRange > 0 and diff <= self.SeekerExplodeAngle
and self.SteerDelay + self.SpawnTime <= CurTime()
and dist < self.SeekerExplodeRange then
local tr = util.TraceLine({
start = self:GetPos(),
endpos = target:GetPos(),
filter = self,
mask = MASK_SOLID,
})
if self.SeekerExplodeSnapPosition then
self:SetPos(tr.HitPos)
end
self:PreDetonate(target)
end
elseif self.NoReacquire then
self.LockOnEntity = nil
self.TargetPos = nil
end
end
elseif (!IsValid(target) and self.NoReacquire) or target.UnTrackable then
self.LockOnEntity = nil
self.TargetPos = nil
end
if self.GunshipWorkaround and (self.GunshipCheck or 0 < CurTime()) then
self.GunshipCheck = CurTime() + 0.33
local tr = util.TraceLine({
start = self:GetPos(),
endpos = self:GetPos() + (self:GetVelocity() * 6 * engine.TickInterval()),
filter = self,
mask = MASK_SHOT
})
if IsValid(tr.Entity) and gunship[tr.Entity:GetClass()] then
self:SetPos(tr.HitPos)
self:PreDetonate(tr.Entity)
end
end
end
function ENT:Think()
if !IsValid(self) or self:GetNoDraw() then return end
if !self.SpawnTime then
self.SpawnTime = CurTime()
end
if SERVER and self.SoundHint and CurTime() >= self.SpawnTime + self.SoundHintDelay then
self.SoundHint = false // only once
sound.EmitHint(SOUND_DANGER, self:GetPos(), self.SoundHintRadius, self.SoundHintDuration, self)
end
if !self.Armed and isnumber(self.TimeFuse) and self.SpawnTime + self.TimeFuse < CurTime() then
self.ArmTime = CurTime()
self.Armed = true
end
if self.Armed and self.ArmTime + self.Delay < CurTime() then
self:PreDetonate()
end
if self.ExplodeUnderwater and self:WaterLevel() > 0 then
self:PreDetonate()
end
if SERVER and self.SpawnTime + self.RocketLifetime > CurTime() then
self:DoTracking()
end
self:DoSmokeTrail()
self:OnThink()
self:NextThink(CurTime())
return true
end
function ENT:Use(ply)
if !self.Defusable then return end
self:EmitSound("TacRP/weapons/rifle_jingle-1.wav")
if self.PickupAmmo then
ply:GiveAmmo(1, self.PickupAmmo, true)
end
self:Remove()
end
function ENT:RemoteDetonate()
self:EmitSound("TacRP/weapons/c4/relay_switch-1.wav")
self.ArmTime = CurTime()
self.Armed = true
end
function ENT:PreDetonate(ent)
if CLIENT then return end
if !self.Detonated then
self.Detonated = true
if !IsValid(self.Attacker) and !IsValid(self:GetOwner()) then self.Attacker = game.GetWorld() end
self:Detonate(ent)
end
end
function ENT:Detonate(ent)
// fill this in :)
end
function ENT:Impact(data, collider)
end
function ENT:SafetyImpact(data, collider)
local attacker = self.Attacker or self:GetOwner()
local ang = data.OurOldVelocity:Angle()
local fx = EffectData()
fx:SetOrigin(data.HitPos)
fx:SetNormal(-ang:Forward())
fx:SetAngles(-ang)
util.Effect("ManhackSparks", fx)
if IsValid(data.HitEntity) then
local dmginfo = DamageInfo()
dmginfo:SetAttacker(attacker)
dmginfo:SetInflictor(self)
dmginfo:SetDamageType(self.ImpactDamageType)
dmginfo:SetDamage(self.ImpactDamage * (self.NPCDamage and 0.25 or 1))
dmginfo:SetDamageForce(data.OurOldVelocity * 20)
dmginfo:SetDamagePosition(data.HitPos)
data.HitEntity:TakeDamageInfo(dmginfo)
end
self:EmitSound("weapons/rpg/shotdown.wav", 80)
if self:GetModel() == "models/weapons/tacint/rocket_deployed.mdl" then
for i = 1, 4 do
local prop = ents.Create("prop_physics")
prop:SetPos(self:GetPos())
prop:SetAngles(self:GetAngles())
prop:SetModel("models/weapons/tacint/rpg7_shrapnel_p" .. i .. ".mdl")
prop:Spawn()
prop:GetPhysicsObject():SetVelocityInstantaneous(data.OurNewVelocity * 0.5 + VectorRand() * 75)
prop:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
SafeRemoveEntityDelayed(prop, 3)
end
end
end
function ENT:ImpactTraceAttack(ent, damage, pen)
if !IsValid(ent) then return end
if ent.LVS then
// LVS only does its penetration logic on FireBullets, so we must fire a bullet to trigger it
self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) // The projectile blocks the penetration decal?!
self:FireBullets({
Attacker = self.Attacker or self:GetOwner(),
Damage = damage,
Tracer = 0,
Src = self:GetPos(),
Dir = self:GetForward(),
HullSize = 16,
Distance = 128,
IgnoreEntity = self,
Callback = function(atk, btr, dmginfo)
dmginfo:SetDamageType(DMG_AIRBOAT + DMG_SNIPER) // LVS wants this
dmginfo:SetDamageForce(self:GetForward() * pen) // penetration strength
end,
})
else
// This is way more consistent because the damage always lands
local tr = util.TraceHull({
start = self:GetPos(),
endpos = self:GetPos() + self:GetForward() * 256,
filter = ent,
whitelist = true,
ignoreworld = true,
mask = MASK_ALL,
mins = Vector( -8, -8, -8 ),
maxs = Vector( 8, 8, 8 ),
})
local dmginfo = DamageInfo()
dmginfo:SetAttacker(self.Attacker or self:GetOwner())
dmginfo:SetInflictor(self)
dmginfo:SetDamagePosition(self:GetPos())
dmginfo:SetDamageForce(self:GetForward() * pen)
dmginfo:SetDamageType(DMG_AIRBOAT + DMG_SNIPER)
dmginfo:SetDamage(damage)
ent:DispatchTraceAttack(dmginfo, tr, self:GetForward())
end
end
function ENT:Stuck()
end
function ENT:DrawTranslucent()
self:Draw()
end
local mat = Material("effects/ar2_altfire1b")
function ENT:Draw()
if self:GetOwner() == LocalPlayer() and (self.SpawnTime + 0.05) > CurTime() then return end
self:DrawModel()
if self.FlareColor then
local mult = self.SafetyFuse and math.Clamp((CurTime() - (self.SpawnTime + self.SafetyFuse)) / self.SafetyFuse, 0.1, 1) or 1
render.SetMaterial(mat)
render.DrawSprite(self:GetPos() + (self:GetAngles():Forward() * -16), mult * math.Rand(self.FlareSizeMin, self.FlareSizeMax), mult * math.Rand(self.FlareSizeMin, self.FlareSizeMax), self.FlareColor)
end
end
function ENT:SwitchTarget(target)
if IsValid(self.LockOnEntity) then
if isfunction(self.LockOnEntity.OnLaserLock) then
self.LockOnEntity:OnLaserLock(false)
end
end
self.LockOnEntity = target
if IsValid(self.LockOnEntity) then
if isfunction(self.LockOnEntity.OnLaserLock) then
self.LockOnEntity:OnLaserLock(true)
end
end
end
hook.Add("EntityTakeDamage", "tacrp_proj_collision", function(ent, dmginfo)
if IsValid(dmginfo:GetInflictor())
and scripted_ents.IsBasedOn(dmginfo:GetInflictor():GetClass(), "tacrp_proj_base")
and dmginfo:GetDamageType() == DMG_CRUSH then dmginfo:SetDamage(0) return true end
end)