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 @@
include("shared.lua")

View File

@@ -0,0 +1,763 @@
AddCSLuaFile("shared.lua")
include("shared.lua")
util.AddNetworkString("MuR_DroneCam")
util.AddNetworkString("MuR_DroneLost")
util.AddNetworkString("MuR_DroneEmergency") -- экстренное самоуничтожение
local rotorSounds = {
"ambient/machines/spin_loop.wav",
"ambient/machines/machine1_hit1.wav"
}
local damageSounds = {
"physics/metal/metal_box_impact_bullet1.wav",
"physics/metal/metal_box_impact_bullet2.wav",
"physics/metal/metal_box_impact_bullet3.wav",
"physics/metal/metal_computer_impact_bullet1.wav",
"physics/metal/metal_computer_impact_bullet2.wav"
}
local startupSounds = {
"buttons/button9.wav",
"buttons/button17.wav"
}
function ENT:Initialize()
self:SetModel("models/murdered/weapons/drone_ex.mdl")
self:ResetSequence("idle")
self:SetHealth(self.HealthCount)
self:SetGravity(0)
self.Velo = {x = 0, y = 0, z = 0}
self:SetSolid(SOLID_VPHYSICS)
self:PhysicsInit(SOLID_VPHYSICS)
self.MultSpeed = 900
self.MaxSpeed = 1000
self.BoostSpeed = 1500
self.MaxPitch = 18
self.MaxRoll = 22
self.LinearDrag = 0.04
self.GrenadeDelay = CurTime() + 5
self.TakeDamageWall = 0
self.DeltaTime = 0
self.LastBoostSound = 0
self.LastWindSound = 0
-- самоуничтожение
self.EmergencyKill = false
self.EmergencyKillTime = 0
self._Exploding = false
self:SetNW2Float("EmergencyCountdown", 0)
self:SetNW2Float("RemoveTime", CurTime() + self.BatteryCount)
self:EmitSound(startupSounds[math.random(#startupSounds)], 60, 100, 0.5)
local ply = self:GetCreator()
if IsValid(ply) then
local forward = ply:GetForward()
forward.z = 0
forward = forward:GetNormalized()
local spawnDist = 80
local spawnHeight = 40
local spawnPos = ply:GetPos() + forward * spawnDist + Vector(0, 0, spawnHeight)
local tr = util.TraceHull({
start = spawnPos,
endpos = spawnPos,
mins = Vector(-20, -20, -20),
maxs = Vector(20, 20, 20),
filter = {ply, self}
})
if tr.Hit then
spawnPos = spawnPos + forward * 60
end
local ground = util.TraceLine({
start = spawnPos,
endpos = spawnPos - Vector(0, 0, 5000),
filter = self
})
if spawnPos.z < ground.HitPos.z + 30 then
spawnPos.z = ground.HitPos.z + 30
end
self:SetPos(spawnPos)
self:SetAngles(Angle(0, ply:EyeAngles().y, 0))
end
-- визуальная ПГ-26 под дроном
local rocket = ents.Create("prop_dynamic")
if IsValid(rocket) then
rocket:SetModel("models/sw/rus/rockets/pg26.mdl")
rocket:SetParent(self)
rocket:SetLocalPos(Vector(-15, 0, 2))
rocket:SetLocalAngles(Angle(0, 0, 0))
rocket:Spawn()
rocket:SetMoveType(MOVETYPE_NONE)
rocket:SetSolid(SOLID_NONE)
rocket:PhysicsDestroy()
self.RocketModel = rocket
end
if CreateSound then
self.RotorSound = CreateSound(self, rotorSounds[1])
if self.RotorSound then
self.RotorSound:PlayEx(0.2, 80)
end
self.WindSound = CreateSound(self, "ambient/wind/wind_snippet2.wav")
if self.WindSound then
self.WindSound:PlayEx(0, 100)
end
end
timer.Simple(0.1, function()
if not IsValid(self) then return end
local ply2 = self:GetCreator()
if not IsValid(ply2) then return end
ply2.ControlDrone = self
net.Start("MuR_DroneCam")
net.WriteEntity(self)
net.Send(ply2)
end)
end
function ENT:OnRemove()
local ply = self:GetCreator()
if IsValid(ply) and ply.ControlDrone == self then
ply.ControlDrone = nil
end
if self.RotorSound then self.RotorSound:Stop() end
if self.WindSound then self.WindSound:Stop() end
self:StopSound("ambient/machines/spin_loop.wav")
self:StopSound("ambient/wind/wind_snippet2.wav")
self:StopSound("vehicles/airboat/fan_blade_fullthrottle_loop1.wav")
end
function ENT:GetForwardAbs()
local ang = self:GetAngles()
ang.x = 0
ang.z = 0
return ang:Forward()
end
function ENT:GetRightAbs()
local ang = self:GetAngles()
ang.x = 0
ang.z = 0
return ang:Right()
end
function ENT:GetUpAbs()
local ang = self:GetAngles()
ang.x = 0
ang.z = 0
return ang:Up()
end
function ENT:Think()
-- самоуничтожение
if self.EmergencyKill then
local phys = self:GetPhysicsObject()
if IsValid(phys) then
phys:EnableMotion(false)
end
if CurTime() >= (self.EmergencyKillTime or 0) then
self:Explode()
return true
end
end
-- Ограничение дистанции как у spy-дрона
local ply = self:GetCreator()
if IsValid(ply) then
local maxDist = 12000 * 12000 -- 12к юнитов
local dist2 = ply:GetPos():DistToSqr(self:GetPos())
if dist2 > maxDist then
net.Start("MuR_DroneLost")
net.Send(ply)
if ply.ControlDrone == self then
ply.ControlDrone = nil
end
self:Explode()
return true
end
end
-- === РЭБ (глушилка) ===
if self:GetNWBool("Jammed") then
-- если впервые попали в РЭБ — запускаем таймер
if not self.JamStart then
self.JamStart = CurTime()
self.JamEnd = CurTime() + 5 -- 5 секунд на выход
end
-- ВАЖНО: управление НЕ отключаем
-- ВАЖНО: скорость НЕ сбрасываем
-- Дрон продолжает лететь как обычно
-- если время вышло — взрываем
if CurTime() >= self.JamEnd then
local ply = self:GetCreator()
if IsValid(ply) then
net.Start("MuR_DroneLost")
net.Send(ply)
if ply.ControlDrone == self then
ply.ControlDrone = nil
end
end
self:Explode()
return true
end
else
-- если вышли из РЭБ — сбрасываем таймер
self.JamStart = nil
self.JamEnd = nil
end
local ply = self:GetCreator()
if not IsValid(ply) then
self:Remove()
return
end
local phys = self:GetPhysicsObject()
local mu = self.MultSpeed
local ms = self.MaxSpeed
local tick = FrameTime()
local ang = ply:EyeAngles().y
local sang = Angle(0, ang, 0)
ply:SetActiveWeapon(nil)
local tr = util.TraceEntity({
start = self:GetPos(),
endpos = self:GetPos(),
filter = self
}, self)
if self:GetNW2Bool("Boost") then
local fwd = self:GetForward()
phys:SetVelocityInstantaneous(fwd * self.Velo.x)
else
if tr.Hit then
phys:SetVelocityInstantaneous(
(self:GetForwardAbs() * self.Velo.x +
self:GetRightAbs() * self.Velo.y +
self:GetUpAbs() * self.Velo.z) / 2
)
if self.TakeDamageWall < CurTime() then
self:TakeDamage(1)
self.TakeDamageWall = CurTime() + 0.2
end
else
phys:SetVelocityInstantaneous(
self:GetForwardAbs() * self.Velo.x +
self:GetRightAbs() * self.Velo.y +
self:GetUpAbs() * self.Velo.z +
Vector(0, 0, 15)
)
end
end
local ratioX = math.Clamp(self.Velo.x / ms, -1, 1)
local ratioY = math.Clamp(self.Velo.y / ms, -1, 1)
local targetPitch = ratioX * self.MaxPitch
local targetRoll = ratioY * self.MaxRoll
sang = Angle(targetPitch, ang, targetRoll)
local b = {}
b.secondstoarrive = 1
b.pos = self:GetPos()
b.angle = sang
b.maxangular = 120
b.maxangulardamp = 50
b.maxspeed = 1000
b.maxspeeddamp = 150
b.dampfactor = 0.4
b.teleportdistance= 0
b.deltatime = CurTime() - self.DeltaTime
phys:ComputeShadowControl(b)
if ply:KeyDown(IN_FORWARD) or self:GetNW2Bool("Boost") then
if self:GetNW2Bool("Boost") then
self.Velo.x = math.Clamp(self.Velo.x + tick * mu * 2, -ms, self.BoostSpeed)
else
self.Velo.x = math.Clamp(self.Velo.x + tick * mu, -ms, ms)
end
elseif ply:KeyDown(IN_BACK) then
self.Velo.x = math.Clamp(self.Velo.x - tick * mu, -ms, ms)
end
if ply:KeyDown(IN_MOVELEFT) then
self.Velo.y = math.Clamp(self.Velo.y - tick * mu, -ms, ms)
elseif ply:KeyDown(IN_MOVERIGHT) then
self.Velo.y = math.Clamp(self.Velo.y + tick * mu, -ms, ms)
end
if ply:KeyDown(IN_DUCK) then
self.Velo.z = math.Clamp(self.Velo.z - tick * mu, -ms, ms)
elseif ply:KeyDown(IN_SPEED) then
self.Velo.z = math.Clamp(self.Velo.z + tick * mu, -ms, ms)
end
local drag = 1 - math.Clamp(self.LinearDrag * tick, 0, 0.2)
self.Velo.x = self.Velo.x * drag
self.Velo.y = self.Velo.y * drag
self.Velo.z = self.Velo.z * (0.98 * drag)
if not ply:KeyDown(IN_FORWARD) and not ply:KeyDown(IN_BACK) and not self:GetNW2Bool("Boost") then
if math.abs(self.Velo.x) > 5 then
if self.Velo.x > 0 then
self.Velo.x = math.Clamp(self.Velo.x - tick * mu, -ms, ms)
else
self.Velo.x = math.Clamp(self.Velo.x + tick * mu, -ms, ms)
end
else
self.Velo.x = 0
end
end
if not ply:KeyDown(IN_SPEED) and not ply:KeyDown(IN_DUCK) then
if math.abs(self.Velo.z) > 5 then
if self.Velo.z > 0 then
self.Velo.z = math.Clamp(self.Velo.z - tick * mu, -ms, ms)
else
self.Velo.z = math.Clamp(self.Velo.z + tick * mu, -ms, ms)
end
else
self.Velo.z = 0
end
end
if not ply:KeyDown(IN_MOVELEFT) and not ply:KeyDown(IN_MOVERIGHT) then
if math.abs(self.Velo.y) > 5 then
if self.Velo.y > 0 then
self.Velo.y = math.Clamp(self.Velo.y - tick * mu, -ms, ms)
else
self.Velo.y = math.Clamp(self.Velo.y + tick * mu, -ms, ms)
end
else
self.Velo.y = 0
end
end
local wasBoost = self:GetNW2Bool("Boost")
self:SetNW2Bool("Boost", ply:KeyDown(IN_ATTACK))
self:SetNW2Bool("Light", ply:KeyDown(IN_ATTACK2))
if ply:KeyDown(IN_ATTACK) and not wasBoost then
self:EmitSound("vehicles/airboat/fan_blade_fullthrottle_loop1.wav", 70, 150, 0.4)
self.LastBoostSound = CurTime()
elseif not ply:KeyDown(IN_ATTACK) and wasBoost then
self:StopSound("vehicles/airboat/fan_blade_fullthrottle_loop1.wav")
end
if self.RotorSound then
local spd = phys:GetVelocity():Length()
local load = math.Clamp(
(math.abs(self.Velo.x) + math.abs(self.Velo.y) + math.abs(self.Velo.z)) / (self.BoostSpeed * 0.6),
0, 1
)
local timeLeft = math.max(self:GetNW2Float("RemoveTime") - CurTime(), 0)
local battFrac = math.Clamp(timeLeft / self.BatteryCount, 0, 1)
local basePitch = 80 + 45 * load + math.Clamp(spd * 0.025, 0, 35)
local baseVol = 0.15 + 0.35 * load
basePitch = basePitch * (0.85 + 0.15 * battFrac)
if battFrac < 0.15 then
basePitch = basePitch + math.sin(CurTime() * 15) * 5
end
self.RotorSound:ChangePitch(math.Clamp(basePitch, 55, 180), 0.08)
self.RotorSound:ChangeVolume(math.Clamp(baseVol, 0.05, 0.55), 0.08)
end
if self.WindSound then
local spd = phys:GetVelocity():Length()
local windVol = math.Clamp(spd / 800, 0, 0.4)
local windPitch = 80 + math.Clamp(spd * 0.05, 0, 40)
self.WindSound:ChangeVolume(windVol, 0.15)
self.WindSound:ChangePitch(windPitch, 0.15)
end
if self:Health() <= 0 then
local ply2 = self:GetCreator()
if IsValid(ply2) then
net.Start("MuR_DroneLost")
net.Send(ply2)
end
self:Explode()
return true
end
local ply2 = self:GetCreator()
if self:GetNW2Float("RemoveTime") < CurTime() or not IsValid(ply2) or not ply2:Alive() or (ply2.GetSVAnim and ply2:GetSVAnim() ~= "") then
if IsValid(ply2) then
net.Start("MuR_DroneLost")
net.Send(ply2)
if ply2.ControlDrone == self then
ply2.ControlDrone = nil
end
end
self:EmitSound("ambient/machines/machine1_hit2.wav", 70, 80, 0.6)
self:Remove()
elseif IsValid(ply2) and ply2:KeyDown(IN_RELOAD) and ply2:GetPos():DistToSqr(self:GetPos()) < 50000 then
ply2:Give("swep_drone")
ply2:SelectWeapon("swep_drone")
self:EmitSound("buttons/button14.wav", 60, 100, 0.5)
if ply2.ControlDrone == self then
ply2.ControlDrone = nil
end
self:Remove()
end
self.DeltaTime = CurTime()
self:NextThink(CurTime())
return true
end
function ENT:PhysicsCollide(col)
if self.Velo.x > self.MaxSpeed and self:GetNW2Bool("Boost") then
self:EmitSound("physics/metal/metal_solid_impact_hard"..math.random(1,5)..".wav", 80, 100, 0.8)
self:TakeDamage(self:Health())
end
end
-- ВЗРЫВ С HEAT-ПРОБИТИЕМ, ПОРТНУТ ИЗ КРОКУСА (исправлен для самоуничтожения)
function ENT:Explode()
if self._Exploding then return end
self._Exploding = true
local ply = self:GetCreator()
if IsValid(ply) then
net.Start("MuR_DroneLost")
net.Send(ply)
if ply.ControlDrone == self then
ply.ControlDrone = nil
end
end
self:OnFinishExplosion()
timer.Simple(0, function()
if IsValid(self) then
self:Remove()
end
end)
end
function ENT:OnFinishExplosion()
local angle_reg = Angle(0,0,0)
local angle_dif = Angle(-90,0,0)
local pos = self:LocalToWorld(self:OBBCenter())
if self:WaterLevel() >= 1 then
local tr = util.TraceLine({
start = pos,
endpos = pos + Vector(0,0,9000),
filter = self,
})
local tr2 = util.TraceLine({
start = tr.HitPos,
endpos = tr.HitPos - Vector(0,0,9000),
filter = self,
mask = MASK_WATER + CONTENTS_TRANSLUCENT,
})
if tr2.Hit then
ParticleEffect(self.EffectWater, tr2.HitPos,(self.AngEffect and angle_dif or angle_reg), nil)
end
else
local tr = util.TraceLine({
start = pos,
endpos = pos - Vector(0, 0, self.TraceLength or 150),
filter = self,
})
if tr.HitWorld then
ParticleEffect(self.Effect, pos, (self.AngEffect and angle_dif or angle_reg), nil)
else
ParticleEffect(self.EffectAir, pos, (self.AngEffect and angle_dif or angle_reg), nil)
end
end
if self.HEAT then
local hitPos = self:GetPos() + self:GetForward() * 20
local heat = {}
heat.Src = hitPos
heat.Dir = self:GetForward()
heat.Spread = Vector(0,0,0)
heat.Force = self.ArmorPenetration + math.Rand(-self.ArmorPenetration * 0.05, self.ArmorPenetration * 0.05)
heat.HullSize = self.HEATRadius * 2
heat.Damage = self.PenetrationDamage + math.Rand(-self.PenetrationDamage * 0.1, self.PenetrationDamage * 0.1)
heat.Velocity = 30000
heat.Attacker = self.Owner or self:GetCreator() or self
if LVS and LVS.FireBullet then
LVS:FireBullet(heat)
end
if self.ExplosionRadius > 0 then
util.BlastDamage(
self,
(IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld(),
pos,
self.ExplosionRadius,
self.ExplosionDamage
)
self:BlastDoors(self, pos, 10)
end
if self.FragCount > 0 then
self:Fragmentation(
self,
pos,
10000,
self.FragDamage,
self.FragRadius,
(IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld()
)
end
else
if self.ExplosionRadius > 0 then
for _, v in pairs(ents.FindInSphere(pos, self.ExplosionRadius)) do
if IsValid(v) and not v.SWBombV3 then
local dmg = DamageInfo()
dmg:SetInflictor(self)
dmg:SetDamage(self.ExplosionDamage)
dmg:SetDamageType(self.DamageType or DMG_BLAST)
dmg:SetAttacker((IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld())
v:TakeDamageInfo(dmg)
end
end
end
if self.BlastRadius > 0 then
util.BlastDamage(
self,
(IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld(),
pos,
self.BlastRadius,
self.ExplosionDamage / 2
)
self:BlastDoors(self, pos, 10)
end
if self.FragCount > 0 then
self:Fragmentation(
self,
pos,
10000,
self.FragDamage,
self.FragRadius,
(IsValid(self:GetCreator()) and self:GetCreator()) or self.Attacker or game.GetWorld()
)
end
end
if swv3 and swv3.CreateSound then
swv3.CreateSound(pos, false, self.ExplosionSound, self.FarExplosionSound, self.DistExplosionSound)
else
sound.Play(self.ExplosionSound or "ambient/explosions/explode_4.wav", pos, 100, 100, 1)
end
end
function ENT:IsDoor(ent)
local Class = ent:GetClass()
return (Class == "prop_door") or (Class == "prop_door_rotating") or (Class == "func_door") or (Class == "func_door_rotating")
end
function ENT:BlastDoors(blaster, pos, power, range, ignoreVisChecks)
for _, door in pairs(ents.FindInSphere(pos, 40 * power * (range or 1))) do
if self:IsDoor(door) then
local proceed = ignoreVisChecks
if not proceed then
local tr = util.QuickTrace(pos, door:LocalToWorld(door:OBBCenter()) - pos, blaster)
proceed = IsValid(tr.Entity) and (tr.Entity == door)
end
if proceed then
self:BlastDoor(door, (door:LocalToWorld(door:OBBCenter()) - pos):GetNormalized() * 1000)
end
end
if door:GetClass() == "func_breakable_surf" then
door:Fire("Break")
end
end
end
function ENT:BlastDoor(ent, vel)
local Moddel, Pozishun, Ayngul, Muteeriul, Skin = ent:GetModel(), ent:GetPos(), ent:GetAngles(), ent:GetMaterial(), ent:GetSkin()
sound.Play("Wood_Crate.Break", Pozishun, 60, 100)
sound.Play("Wood_Furniture.Break", Pozishun, 60, 100)
ent:Fire("unlock", "", 0)
ent:Fire("open", "", 0)
ent:SetNoDraw(true)
ent:SetNotSolid(true)
if Moddel and Pozishun and Ayngul then
local Replacement = ents.Create("prop_physics")
Replacement:SetModel(Moddel)
Replacement:SetPos(Pozishun + Vector(0, 0, 1))
Replacement:SetAngles(Ayngul)
if Muteeriul then
Replacement:SetMaterial(Muteeriul)
end
if Skin then
Replacement:SetSkin(Skin)
end
Replacement:SetModelScale(.9, 0)
Replacement:Spawn()
Replacement:Activate()
if vel then
Replacement:GetPhysicsObject():SetVelocity(vel)
timer.Simple(0, function()
if IsValid(Replacement) then
Replacement:GetPhysicsObject():ApplyForceCenter(vel * 100)
end
end)
end
timer.Simple(3, function()
if IsValid(Replacement) then
Replacement:SetCollisionGroup(COLLISION_GROUP_WEAPON)
end
end)
timer.Simple(30, function()
if IsValid(ent) then
ent:SetNotSolid(false)
ent:SetNoDraw(false)
end
if IsValid(Replacement) then
Replacement:Remove()
end
end)
end
end
function ENT:Fragmentation(shooter, origin, fragNum, fragDmg, fragMaxDist, attacker, direction, spread, zReduction)
shooter = shooter or game.GetWorld()
zReduction = zReduction or 2
local Spred = Vector(0, 0, 0)
local BulletsFired, MaxBullets, disperseTime = 0, self.FragCount, .5
if fragNum >= 12000 then
disperseTime = 2
elseif fragNum >= 6000 then
disperseTime = 1
end
for i = 1, fragNum do
timer.Simple((i / fragNum) * disperseTime, function()
local Dir
if direction and spread then
Dir = Vector(direction.x, direction.y, direction.z)
Dir = Dir + VectorRand() * math.Rand(0, spread)
Dir:Normalize()
else
Dir = VectorRand()
end
if zReduction then
Dir.z = Dir.z / zReduction
Dir:Normalize()
end
local Tr = util.QuickTrace(origin, Dir * fragMaxDist, shooter)
if Tr.Hit and not Tr.HitSky and not Tr.HitWorld and (BulletsFired < MaxBullets) then
local LowFrag = (Tr.Entity.IsVehicle and Tr.Entity:IsVehicle()) or Tr.Entity.LFS or Tr.Entity.LVS or Tr.Entity.EZlowFragPlease
if (not LowFrag) or (LowFrag and math.random(1, 4) == 2) then
local firer = (IsValid(shooter) and shooter) or game.GetWorld()
firer:FireBullets({
Attacker = attacker,
Damage = fragDmg,
Force = fragDmg / 8,
Num = 1,
Src = origin,
Tracer = 0,
Dir = Dir,
Spread = Spred,
AmmoType = "Buckshot"
})
BulletsFired = BulletsFired + 1
end
end
end)
end
end
function ENT:OnTakeDamage(dmgt)
local dmg = dmgt:GetDamage()
self:SetHealth(self:Health() - dmg)
local owner = self:GetCreator()
if IsValid(owner) then
owner:ViewPunch(AngleRand(-1, 1))
local snd = damageSounds[math.random(#damageSounds)]
self:EmitSound(snd, 75, math.random(90, 110))
if self:Health() < self.HealthCount * 0.3 and math.random() < 0.3 then
self:EmitSound("ambient/machines/machine1_hit1.wav", 65, math.random(120, 140), 0.4)
end
end
end
hook.Add("SetupPlayerVisibility", "GpincDroneCam", function(ply, viewEntity)
local drone = ply.ControlDrone
if IsValid(drone) and drone:GetClass() == "mur_drone_entity" then
AddOriginToPVS(drone:GetPos())
end
end)
-- приём команды экстренного самоуничтожения
net.Receive("MuR_DroneEmergency", function(len, ply)
local drone = ply.ControlDrone
if not IsValid(drone) or drone:GetClass() ~= "mur_drone_entity" then
ply.ControlDrone = nil
return
end
if not drone.EmergencyKill then
drone.EmergencyKill = true
drone.EmergencyKillTime = CurTime() + 3
drone:SetNWFloat("EmergencyCountdown", drone.EmergencyKillTime)
drone:EmitSound("buttons/button17.wav", 75, 100, 0.6)
end
end)

View File

@@ -0,0 +1,541 @@
ENT.Base = "base_gmodentity"
ENT.Type = "anim"
ENT.PrintName = "FPV дрон"
ENT.Spawnable = true
ENT.AutomaticFrameAdvance = true
-- === ПАРАМЕТРЫ, НУЖНЫЕ И СЕРВЕРУ, И КЛИЕНТУ ===
ENT.BatteryCount = 60
ENT.HealthCount = 2
-- HEAT / Кумулятивная боеголовка
ENT.HEAT = true
ENT.HEATRadius = 2
ENT.ArmorPenetration = 50000
ENT.PenetrationDamage = 2500
-- Взрыв
ENT.ExplosionDamage = 300
ENT.ExplosionRadius = 75
ENT.BlastRadius = 0
-- Фрагментация
ENT.FragDamage = 25
ENT.FragRadius = 300
ENT.FragCount = 100
-- Эффекты
ENT.TraceLength = 150
ENT.AngEffect = true
-- LVS эффекты
ENT.Effect = "lvs_explosion_small"
ENT.EffectAir = "lvs_explosion_small"
ENT.EffectWater = "lvs_explosion_small"
-- LVS звуки
ENT.ExplosionSound = "lvs/explosion_small.wav"
ENT.FarExplosionSound = "lvs/explosion_small.wav"
ENT.DistExplosionSound = "lvs/explosion_small.wav"
if CLIENT then
local droneMat = Material("vgui/gradient-l")
local scanlineMat = Material("pp/texturize")
local noiseChars = {"", "", "", "", "", "", "", ""}
-- Ссылка на активный дрон (локально)
local DRONE = nil
-- Уникальные имена хуков, чтобы можно было безопасно удалять
local HOOK_RENDER_SSE = "FPV_Drone_RenderScreenspaceEffects"
local HOOK_HUDPAINT = "FPV_Drone_HUDPaint"
local HOOK_CREATEMOVE = "FPV_Drone_CreateMove"
local HOOK_CALCVIEW = "FPV_Drone_CalcView"
local HOOK_CLEANUP_THINK = "FPV_Drone_CleanupThink"
local HOOK_EMERGENCY = "FPV_Drone_EmergencyKey"
local function DrawScanlines(sw, sh, intensity)
surface.SetDrawColor(0, 0, 0, intensity * 15)
for i = 0, sh, 4 do
surface.DrawRect(0, i, sw, 1)
end
end
local function DrawGlitchNoise(sw, sh, intensity)
if intensity < 0.3 then return end
local glitchCount = math.floor((1 - intensity) * 50)
surface.SetDrawColor(255, 255, 255, (1 - intensity) * 100)
for i = 1, glitchCount do
local x = math.random(0, sw)
local y = math.random(0, sh)
local w = math.random(20, 200)
surface.DrawRect(x, y, w, 2)
end
end
local function DrawCornerBrackets(x, y, w, h, size, thickness, col)
surface.SetDrawColor(col)
surface.DrawRect(x, y, size, thickness)
surface.DrawRect(x, y, thickness, size)
surface.DrawRect(x + w - size, y, size, thickness)
surface.DrawRect(x + w - thickness, y, thickness, size)
surface.DrawRect(x, y + h - thickness, size, thickness)
surface.DrawRect(x, y + h - size, thickness, size)
surface.DrawRect(x + w - size, y + h - thickness, size, thickness)
surface.DrawRect(x + w - thickness, y + h - size, thickness, size)
end
local function DrawCrosshair(cx, cy, size, gap, thickness, col)
surface.SetDrawColor(col)
surface.DrawRect(cx - size, cy - thickness/2, size - gap, thickness)
surface.DrawRect(cx + gap, cy - thickness/2, size - gap, thickness)
surface.DrawRect(cx - thickness/2, cy - size, thickness, size - gap)
surface.DrawRect(cx - thickness/2, cy + gap, thickness, size - gap)
end
local function DrawArcSegment(cx, cy, radius, startAng, endAng, thickness, segments, col)
surface.SetDrawColor(col)
local step = (endAng - startAng) / segments
for i = 0, segments - 1 do
local a1 = math.rad(startAng + i * step)
local a2 = math.rad(startAng + (i + 1) * step)
local x1 = cx + math.cos(a1) * radius
local y1 = cy + math.sin(a1) * radius
local x2 = cx + math.cos(a2) * radius
local y2 = cy + math.sin(a2) * radius
surface.DrawLine(x1, y1, x2, y2)
end
end
local function DrawCircularBar(cx, cy, radius, frac, thickness, bgCol, fgCol)
DrawArcSegment(cx, cy, radius, -90, 270, thickness, 32, bgCol)
if frac > 0 then
DrawArcSegment(cx, cy, radius, -90, -90 + 360 * frac, thickness, math.floor(32 * frac), fgCol)
end
end
local function GetDroneRT()
local name = "MuR_Drone_LastFrame"
return GetRenderTarget(name, ScrW(), ScrH())
end
local lastFrameMat = CreateMaterial("MuR_Drone_LastFrame_Mat", "UnlitGeneric", {
["$basetexture"] = "MuR_Drone_LastFrame",
["$ignorez"] = 1,
["$vertexcolor"] = 1,
["$vertexalpha"] = 1
})
local function LoseDrone()
surface.PlaySound("ambient/levels/prison/radio_random"..math.random(1,15)..".wav")
local startTime = CurTime()
lastFrameMat:SetTexture("$basetexture", GetDroneRT())
hook.Add("HUDPaint", "FPV_Drone_LostHUD", function()
if not LocalPlayer():Alive() then return end
local sw, sh = ScrW(), ScrH()
local elapsed = CurTime() - startTime
local flicker = math.sin(elapsed * 30) > 0
surface.SetDrawColor(0, 0, 0, 255)
surface.DrawRect(0, 0, sw, sh)
surface.SetDrawColor(255, 255, 255, 255)
surface.SetMaterial(lastFrameMat)
surface.DrawTexturedRect(0, 0, sw, sh)
surface.SetDrawColor(0, 0, 0, math.min(elapsed * 100, 200))
surface.DrawRect(0, 0, sw, sh)
for i = 1, 150 do
local px = math.random(0, sw)
local py = math.random(0, sh)
local ps = math.random(2, 8)
local c = math.random(30, 80)
surface.SetDrawColor(c, c, c, 255)
surface.DrawRect(px, py, ps, ps)
end
DrawScanlines(sw, sh, 3)
for i = 1, 20 do
local y = math.random(0, sh)
local w = math.random(sw * 0.3, sw)
local x = math.random(0, sw - w)
surface.SetDrawColor(40, 40, 45, 200)
surface.DrawRect(x, y, w, math.random(1, 5))
end
for i = 1, 2000 do
surface.SetDrawColor(255, 255, 255, math.random(10, 80))
surface.DrawRect(math.random(0, sw), math.random(0, sh), 1, 1)
end
local textCol = flicker and Color(255, 50, 50, 255) or Color(200, 40, 40, 200)
draw.SimpleText("▌▌ СОЕДИНЕНИЕ ПОТЕРЯНО ▐▐", "MuR_Font5", sw/2, sh/2 - 30, textCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText("СВЯЗЬ ПРЕКРАЩЕНА", "MuR_Font2", sw/2, sh/2 + 30, Color(150, 150, 150, 200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end)
timer.Simple(2, function()
hook.Remove("HUDPaint", "FPV_Drone_LostHUD")
end)
end
net.Receive("MuR_DroneLost", LoseDrone)
local function FPV_ClearHooks()
hook.Remove("HUDPaint", HOOK_HUDPAINT)
hook.Remove("RenderScreenspaceEffects", HOOK_RENDER_SSE)
hook.Remove("CalcView", HOOK_CALCVIEW)
hook.Remove("CreateMove", HOOK_CREATEMOVE)
end
-- Основной приём дрона: ставим локальную ссылку и создаём HUD/CalcView хуки
net.Receive("MuR_DroneCam", function()
FPV_ClearHooks()
local ent = net.ReadEntity()
DRONE = ent
-- Если дрон невалиден — ничего не делаем
if not IsValid(DRONE) then return end
-- Переменные HUD
local pulseAlpha = 0
local pulseDir = 1
local lastBattBeep = 0
local lastDmgBeep = 0
local startTime = CurTime()
local smoothBatt = 1
local smoothHP = 1
local ppTab = {
["$pp_colour_addr"] = 0,
["$pp_colour_addg"] = 0.02,
["$pp_colour_addb"] = 0,
["$pp_colour_brightness"] = -0.03,
["$pp_colour_contrast"] = 1.15,
["$pp_colour_colour"] = 0.4,
["$pp_colour_mulr"] = 0,
["$pp_colour_mulg"] = 0.05,
["$pp_colour_mulb"] = 0
}
-- RenderScreenspaceEffects
hook.Add("RenderScreenspaceEffects", HOOK_RENDER_SSE, function()
if not IsValid(DRONE) then return end
local rt = GetDroneRT()
render.CopyRenderTargetToTexture(rt)
local timeLeft = math.max(DRONE:GetNW2Float("RemoveTime") - CurTime(), 0)
local battFrac = math.Clamp(timeLeft / DRONE.BatteryCount, 0, 1)
if battFrac < 0.2 then
ppTab["$pp_colour_addr"] = 0.1 * (1 - battFrac / 0.2)
ppTab["$pp_colour_contrast"] = 1.15 + 0.2 * (1 - battFrac / 0.2)
else
ppTab["$pp_colour_addr"] = 0
ppTab["$pp_colour_contrast"] = 1.15
end
DrawColorModify(ppTab)
DrawSharpen(0.8, 0.8)
end)
-- HUDPaint
hook.Add("HUDPaint", HOOK_HUDPAINT, function()
if not IsValid(DRONE) then return end
local ent = DRONE
local sw, sh = ScrW(), ScrH()
local cx, cy = sw / 2, sh / 2
local time = CurTime()
local elapsed = time - startTime
pulseAlpha = pulseAlpha + pulseDir * FrameTime() * 400
if pulseAlpha >= 255 then pulseDir = -1 pulseAlpha = 255
elseif pulseAlpha <= 100 then pulseDir = 1 pulseAlpha = 100 end
local timeLeft = math.max(ent:GetNW2Float("RemoveTime") - CurTime(), 0)
local battFrac = math.Clamp(timeLeft / ent.BatteryCount, 0, 1)
local hpFrac = math.Clamp(ent:Health() / ent.HealthCount, 0, 1)
smoothBatt = Lerp(FrameTime() * 3, smoothBatt, battFrac)
smoothHP = Lerp(FrameTime() * 5, smoothHP, hpFrac)
local vel = ent:GetVelocity():Length()
local spd = math.Round(vel * 0.068)
local tr = util.TraceLine({start = ent:GetPos(), endpos = ent:GetPos() - Vector(0, 0, 10000), filter = ent})
local agl = math.Round(ent:GetPos().z - tr.HitPos.z)
local dist2 = LocalPlayer():GetPos():DistToSqr(ent:GetPos())
local signal = math.Clamp(1 - (dist2 / (5000 * 5000)), 0, 1)
local canDisarm = dist2 < 50000
local jamStrength = signal
if jamStrength < 0.4 then
DrawGlitchNoise(sw, sh, jamStrength)
end
if jamStrength < 0.2 then
surface.SetDrawColor(255, 255, 255, math.random(20, 60))
surface.DrawRect(0, 0, sw, sh)
end
if jamStrength < 0.1 then
local shift = math.random(-10, 10)
surface.SetDrawColor(255, 255, 255, 40)
surface.DrawRect(shift, 0, sw, sh)
end
local killTime = ent:GetNWFloat("EmergencyCountdown", 0)
DrawScanlines(sw, sh, 32)
DrawGlitchNoise(sw, sh, signal)
local frameCol = Color(40, 255, 120, 120)
DrawCornerBrackets(40, 40, sw - 80, sh - 80, 60, 3, frameCol)
local crossCol = Color(40, 255, 120, pulseAlpha)
DrawCrosshair(cx, cy, 25, 8, 2, crossCol)
surface.SetDrawColor(40, 255, 120, 60)
DrawArcSegment(cx, cy, 45, 0, 360, 1, 64, Color(40, 255, 120, 40))
local targetDist = tr.HitPos:Distance(ent:GetPos())
if targetDist < 500 then
local lockCol = Color(255, 80, 60, pulseAlpha)
DrawCrosshair(cx, cy, 35, 5, 3, lockCol)
draw.SimpleText(string.format("%.1fm", targetDist * 0.0254), "MuR_Font2", cx, cy + 50, lockCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
end
draw.SimpleText("◈ FPV ", "MuR_Font3", 80, 60, Color(40, 255, 120, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
draw.SimpleText(string.format("ВРЕМЯ СВЯЗИ %.1fs", elapsed), "MuR_Font1", 80, 95, Color(150, 150, 150, 200), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
draw.SimpleText(os.date("%H:%M:%S"), "MuR_Font1", 80, 115, Color(150, 150, 150, 200), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
local telemX = sw - 80
local spdCol = spd > 60 and Color(255, 200, 60) or Color(40, 255, 120)
draw.SimpleText(string.format("%03d km/h", spd), "MuR_Font3", telemX, 60, spdCol, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP)
draw.SimpleText("СКОР", "MuR_Font1", telemX, 95, Color(100, 100, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP)
draw.SimpleText(string.format("%04d m", agl), "MuR_Font3", telemX, 120, Color(40, 255, 120), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP)
draw.SimpleText("ВЫС", "MuR_Font1", telemX, 155, Color(100, 100, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP)
local sigCol = signal < 0.25 and Color(255, 60, 60) or (signal < 0.5 and Color(255, 200, 60) or Color(40, 255, 120))
local sigBars = math.floor(signal * 5)
local sigStr = string.rep("", sigBars) .. string.rep("", 5 - sigBars)
draw.SimpleText(sigStr, "MuR_Font2", telemX, 180, sigCol, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP)
draw.SimpleText(string.format("%d%%", math.Round(signal * 100)), "MuR_Font1", telemX, 210, sigCol, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP)
local barW, barH = 250, 12
local barX, barY = 80, sh - 180
local battCol = smoothBatt < 0.2 and Color(255, 60, 60) or (smoothBatt < 0.4 and Color(255, 200, 60) or Color(40, 255, 120))
surface.SetDrawColor(30, 30, 30, 200)
surface.DrawRect(barX, barY, barW, barH)
surface.SetDrawColor(battCol.r, battCol.g, battCol.b, 255)
surface.DrawRect(barX + 2, barY + 2, (barW - 4) * smoothBatt, barH - 4)
surface.SetDrawColor(battCol.r, battCol.g, battCol.b, 100)
surface.DrawOutlinedRect(barX, barY, barW, barH, 1)
draw.SimpleText(string.format("ЗАРЯД %d%%", math.Round(smoothBatt * 100)), "MuR_Font1", barX, barY - 20, battCol, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM)
draw.SimpleText(string.format("%.0fs", timeLeft), "MuR_Font1", barX + barW, barY - 20, Color(150, 150, 150), TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM)
barY = sh - 130
local hpCol = smoothHP < 0.3 and Color(255, 60, 60) or (smoothHP < 0.6 and Color(255, 200, 60) or Color(40, 255, 120))
surface.SetDrawColor(30, 30, 30, 200)
surface.DrawRect(barX, barY, barW, barH)
surface.SetDrawColor(hpCol.r, hpCol.g, hpCol.b, 255)
surface.DrawRect(barX + 2, barY + 2, (barW - 4) * smoothHP, barH - 4)
surface.SetDrawColor(hpCol.r, hpCol.g, hpCol.b, 100)
surface.DrawOutlinedRect(barX, barY, barW, barH, 1)
draw.SimpleText(string.format("ПРОЧНОСТЬ %d%%", math.Round(smoothHP * 100)), "MuR_Font1", barX, barY - 20, hpCol, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM)
local ctrlX, ctrlY = sw - 80, sh - 200
local ctrlCol = Color(150, 150, 150, 200)
draw.SimpleText("▶ [J] САМОУНИЧТОЖЕНИЕ", "MuR_Font1", ctrlX, ctrlY, Color(255, 80, 80, 200), TEXT_ALIGN_RIGHT)
draw.SimpleText("▶ [ЛКМ] УСКОРЕНИЕ", "MuR_Font1", ctrlX, ctrlY + 22, ctrlCol, TEXT_ALIGN_RIGHT)
draw.SimpleText("▲ [SHIFT] ВВЕРХ", "MuR_Font1", ctrlX, ctrlY + 44, ctrlCol, TEXT_ALIGN_RIGHT)
draw.SimpleText("▼ [CTRL] ВНИЗ", "MuR_Font1", ctrlX, ctrlY + 66, ctrlCol, TEXT_ALIGN_RIGHT)
if canDisarm then
draw.SimpleText("◉ [R] ВЕРНУТЬ ДРОН", "MuR_Font1", ctrlX, ctrlY + 88, Color(40, 255, 120, 200), TEXT_ALIGN_RIGHT)
else
draw.SimpleText("✖ СЛИШКОМ ДАЛЕКО", "MuR_Font1", ctrlX, ctrlY + 88, Color(255, 100, 60, 200), TEXT_ALIGN_RIGHT)
end
if killTime > CurTime() then
local left = math.max(0, killTime - CurTime())
local flash = math.sin(CurTime() * 10) > 0 and 255 or 120
draw.SimpleText(
string.format("САМОУНИЧТОЖЕНИЕ: %.1f", left),
"MuR_Font3",
cx,
260,
Color(255, 50, 50, flash),
TEXT_ALIGN_CENTER
)
end
if ent:GetNW2Bool("Boost") then
local boostFlash = math.sin(time * 15) > 0 and 255 or 180
draw.SimpleText("◀◀ УСКОРЕНИЕ ▶▶", "MuR_Font2", cx, sh - 100, Color(255, 200, 60, boostFlash), TEXT_ALIGN_CENTER)
end
if smoothBatt < 0.15 then
local warnFlash = math.sin(time * 8) > 0 and 255 or 100
draw.SimpleText("⚠ НИЗКИЙ ЗАРЯД ⚠", "MuR_Font4", cx, 80, Color(255, 60, 60, warnFlash), TEXT_ALIGN_CENTER)
if time > lastBattBeep then
surface.PlaySound("buttons/button17.wav")
lastBattBeep = time + 0.8
end
end
if smoothHP < 0.25 then
local dmgFlash = math.sin(time * 6) > 0 and 255 or 100
draw.SimpleText("⚠ КРИТИЧЕСКИЕ ПОВРЕЖДЕНИЯ ⚠", "MuR_Font3", cx, 130, Color(255, 120, 60, dmgFlash), TEXT_ALIGN_CENTER)
if time > lastDmgBeep then
surface.PlaySound("buttons/button10.wav")
lastDmgBeep = time + 1.5
end
end
if signal < 0.2 then
local sigFlash = math.sin(time * 10) > 0 and 255 or 100
draw.SimpleText("◢◤ СЛАБЫЙ СИГНАЛ ◢◤", "MuR_Font3", cx, 180, Color(255, 80, 60, sigFlash), TEXT_ALIGN_CENTER)
end
if ent:GetNWBool("Jammed") then
local jamStrength = ent:GetNWFloat("JamStrength", 1)
if DrawHeavySignalNoise then
DrawHeavySignalNoise(jamStrength)
end
local jamFlash = math.sin(time * 12) > 0 and 255 or 120
draw.SimpleText("◢◤ ОБНАРУЖЕНЫ ПОМЕХИ!!! ◢◤", "MuR_Font3", cx, 220, Color(255, 50, 50, jamFlash), TEXT_ALIGN_CENTER)
end
end)
-- CreateMove: блокируем прыжок/использование, но не трогаем селектор
hook.Add("CreateMove", HOOK_CREATEMOVE, function(cmd)
if not IsValid(DRONE) then
hook.Remove("CreateMove", HOOK_CREATEMOVE)
return
end
cmd:SetForwardMove(0)
cmd:SetSideMove(0)
cmd:RemoveKey(IN_JUMP)
cmd:RemoveKey(IN_USE)
end)
-- CalcView: камера FPV
hook.Add("CalcView", HOOK_CALCVIEW, function(ply, pos, angles, fov)
if not IsValid(DRONE) then
hook.Remove("CalcView", HOOK_CALCVIEW)
hook.Remove("CreateMove", HOOK_CREATEMOVE)
hook.Remove("RenderScreenspaceEffects", HOOK_RENDER_SSE)
hook.Remove("HUDPaint", HOOK_HUDPAINT)
return
end
local ent = DRONE
local dronePos = ent:GetPos()
local droneAng = ent:GetAngles()
local droneVel = ent:GetVelocity()
local speed = droneVel:Length()
local timeLeft = math.max(ent:GetNW2Float("RemoveTime") - CurTime(), 0)
local battFrac = math.Clamp(timeLeft / ent.BatteryCount, 0, 1)
local hpFrac = math.Clamp(ent:Health() / ent.HealthCount, 0, 1)
local baseOffset = Vector(-8, 0, -2)
local speedOffset = math.Clamp(speed * 0.015, 0, 5)
baseOffset.x = baseOffset.x - speedOffset
local camPos = dronePos + ent:GetForward() * baseOffset.x + ent:GetUp() * baseOffset.z
local camAng = Angle(droneAng.p, droneAng.y, droneAng.r)
local eyeAng = ply:EyeAngles()
camAng.p = eyeAng.p
local time = CurTime()
local bobScale = 0.3 + (1 - battFrac) * 0.5
camAng.p = camAng.p + math.sin(time * 2.5) * bobScale
camAng.r = camAng.r + math.cos(time * 1.8) * bobScale * 0.7
if hpFrac < 0.3 then
local shake = (0.3 - hpFrac) * 3
camAng.p = camAng.p + math.Rand(-shake, shake)
camAng.r = camAng.r + math.Rand(-shake, shake)
end
if battFrac < 0.15 then
local flicker = (0.15 - battFrac) * 10
camAng.p = camAng.p + math.sin(time * 20) * flicker
end
local baseFov = 40
local speedFovBoost = math.Clamp(speed * 0.025, 0, 15)
local dynamicFov = baseFov + speedFovBoost
if ent:GetNW2Bool("Boost") then
dynamicFov = dynamicFov + 10
end
return {
origin = camPos,
angles = camAng,
fov = math.Clamp(dynamicFov, 70, 120),
drawviewer = true
}
end)
end)
local KillKeyHeld = false
hook.Add("Think", "FPV_Drone_EmergencyKey", function()
if not IsValid(DRONE) then KillKeyHeld = false return end
if input.IsKeyDown(KEY_J) and not KillKeyHeld then
KillKeyHeld = true
net.Start("MuR_DroneEmergency")
net.SendToServer()
end
if not input.IsKeyDown(KEY_J) then
KillKeyHeld = false
end
end)
-- Подстраховка: если дрон удалён — очистим ссылку и хуки
hook.Add("Think", HOOK_CLEANUP_THINK, function()
if DRONE and not IsValid(DRONE) then
DRONE = nil
-- Удаляем наши хуки, если они остались
hook.Remove("CalcView", HOOK_CALCVIEW)
hook.Remove("CreateMove", HOOK_CREATEMOVE)
hook.Remove("RenderScreenspaceEffects", HOOK_RENDER_SSE)
hook.Remove("HUDPaint", HOOK_HUDPAINT)
hook.Remove("Think", HOOK_EMERGENCY)
hook.Remove("Think", HOOK_CLEANUP_THINK)
end
end)
local function FPV_ClearHooks()
hook.Remove("Think", "FPV_DroneEmergencyKey")
hook.Remove("Think", "fpv_drone_cleanup_on_remove")
hook.Remove("HUDPaint", "FPV_Drone_HUDPaint")
hook.Remove("RenderScreenspaceEffects", "FPV_Drone_RenderScreenspaceEffects")
hook.Remove("CalcView", "FPV_Drone_CalcView")
hook.Remove("CreateMove", "FPV_Drone_CreateMove")
end
hook.Add("EntityRemoved", "FPV_Drone_GlobalCleanup", function(ent)
if ent == DRONE then
DRONE = nil
FPV_ClearHooks()
end
end)
end