1162 lines
40 KiB
Lua
1162 lines
40 KiB
Lua
AddCSLuaFile()
|
||
|
||
if CLIENT then
|
||
if buildermenu then buildermenu:Remove() end
|
||
SWEP.BounceWeaponIcon = false
|
||
language.Add("weapon_builder", "Builder")
|
||
end
|
||
|
||
if SERVER then
|
||
util.AddNetworkString("builder_hitprop")
|
||
end
|
||
|
||
nextreload = 0
|
||
|
||
Builder_EntitiesTBL = {
|
||
["sw_sania"] = {
|
||
cost = 40,
|
||
Name = "sw_sania",
|
||
PrintName = "РЭБ‑модуль",
|
||
mdl = "models/shtormer/sania.mdl",
|
||
lua = true,
|
||
attachToVehicle = true
|
||
}
|
||
}
|
||
|
||
Builder_EntitiesNum = 0
|
||
Builder_EntitiesLoaded = false
|
||
|
||
timer.Simple(1, function()
|
||
local tab, ent = list.Get("SpawnableEntities"), scripted_ents.GetList()
|
||
local tab2 = list.Get("NPC")
|
||
|
||
for k, v in pairs(Builder_EntitiesTBL) do
|
||
if v.Name == "prop_physics" then
|
||
Builder_EntitiesNum = Builder_EntitiesNum + 1
|
||
continue
|
||
end
|
||
|
||
if not istable(tab[v.Name]) and not istable(tab2[v.Name]) then
|
||
Builder_EntitiesTBL[k] = nil
|
||
continue
|
||
end
|
||
if v.lua and not istable(ent[v.Name]) then
|
||
Builder_EntitiesTBL[k] = nil
|
||
continue
|
||
end
|
||
Builder_EntitiesNum = Builder_EntitiesNum + 1
|
||
end
|
||
|
||
Builder_EntitiesLoaded = true
|
||
end)
|
||
|
||
function BuilderIsCreature(ent)
|
||
return (ent:IsPlayer() or ent:IsNPC() or ent:IsNextBot() or ent:GetClass():find("prop_ragdoll"))
|
||
end
|
||
|
||
SWEP.PrintName = "Builder"
|
||
SWEP.Slot = 5
|
||
SWEP.SlotPos = 10
|
||
SWEP.DrawAmmo = false
|
||
SWEP.HoldType = "melee"
|
||
SWEP.DrawCrosshair = false
|
||
SWEP.Spawnable = true
|
||
SWEP.AdminSpawnable = true
|
||
SWEP.ViewModelFOV = 65
|
||
SWEP.ViewModelFlip = false
|
||
SWEP.UseHands = true
|
||
|
||
SWEP.ViewModel = "models/weapons/c_hammer1.mdl"
|
||
SWEP.WorldModel = "models/weapons/w_hammer1.mdl"
|
||
|
||
SWEP.Primary.ClipSize = -1
|
||
SWEP.Primary.DefaultClip = -1
|
||
SWEP.Primary.Automatic = true
|
||
SWEP.Primary.Ammo = "none"
|
||
SWEP.Primary.Delay = 0.5
|
||
SWEP.Primary.Force = 150
|
||
SWEP.Primary.Damage = 30
|
||
|
||
SWEP.Secondary.ClipSize = -1
|
||
SWEP.Secondary.DefaultClip = -1
|
||
SWEP.Secondary.Automatic = true
|
||
SWEP.Secondary.Ammo = "none"
|
||
|
||
function SWEP:Initialize()
|
||
self:SetHoldType(self.HoldType)
|
||
self:SetWeaponHoldType(self.HoldType)
|
||
|
||
self.buildEnt = NULL
|
||
self.cooldwn = CurTime()
|
||
self.ToBuildCost = 10
|
||
self.ToBuild = "combine_mine"
|
||
self.ToBuildmdl = "models/props_combine/combine_mine01.mdl"
|
||
self.ToBuildname = "Combine Mine"
|
||
|
||
self.Idle = 0
|
||
self.IdleTimer = CurTime() + 1
|
||
|
||
if CLIENT then
|
||
self.Ghost = nil
|
||
end
|
||
end
|
||
|
||
function SWEP:Deploy()
|
||
self:SendWeaponAnim(ACT_VM_DRAW)
|
||
self:SetNextPrimaryFire(CurTime() + self:SequenceDuration())
|
||
self:SetNextSecondaryFire(CurTime() + self:SequenceDuration())
|
||
|
||
self.buildEnt = NULL
|
||
self.cooldwn = CurTime()
|
||
|
||
self.Idle = 0
|
||
self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration()
|
||
self:NextThink(CurTime() + self:SequenceDuration())
|
||
|
||
if CLIENT then
|
||
if IsValid(self.Ghost) then self.Ghost:Remove() end
|
||
self.Ghost = nil
|
||
end
|
||
|
||
return true
|
||
end
|
||
|
||
function SWEP:Holster()
|
||
if CLIENT and IsValid(self.Ghost) then
|
||
self.Ghost:Remove()
|
||
self.Ghost = nil
|
||
end
|
||
return true
|
||
end
|
||
|
||
function SWEP:OnRemove()
|
||
if CLIENT and IsValid(self.Ghost) then
|
||
self.Ghost:Remove()
|
||
self.Ghost = nil
|
||
end
|
||
end
|
||
|
||
function SWEP:Think()
|
||
if SERVER then return end
|
||
|
||
local ply = self.Owner
|
||
if not IsValid(ply) then return end
|
||
|
||
local tr = util.TraceLine({
|
||
start = ply:GetShootPos(),
|
||
endpos = ply:GetShootPos() + ply:GetAimVector() * 200,
|
||
filter = ply
|
||
})
|
||
|
||
if not IsValid(self.Ghost) or self.Ghost:GetModel() ~= self.ToBuildmdl then
|
||
if IsValid(self.Ghost) then self.Ghost:Remove() end
|
||
self.Ghost = ClientsideModel(self.ToBuildmdl, RENDERGROUP_TRANSLUCENT)
|
||
self.Ghost:SetRenderMode(RENDERMODE_TRANSCOLOR)
|
||
self.Ghost:SetColor(Color(255,255,255,80))
|
||
end
|
||
|
||
if IsValid(self.Ghost) then
|
||
self.Ghost:SetPos(tr.HitPos)
|
||
self.Ghost:SetAngles(Angle(0, ply:EyeAngles().y, 0))
|
||
end
|
||
end
|
||
function SWEP:PrimaryAttack()
|
||
self.Owner:LagCompensation(true)
|
||
if self._nextBuild and self._nextBuild > CurTime() then
|
||
self.Owner:LagCompensation(false)
|
||
return
|
||
end
|
||
self._nextBuild = CurTime() + 0.3
|
||
|
||
local tr = util.TraceLine({
|
||
start = self.Owner:GetShootPos(),
|
||
endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200,
|
||
filter = self.Owner,
|
||
mask = MASK_SHOT_HULL,
|
||
})
|
||
|
||
if not IsValid(tr.Entity) then
|
||
tr = util.TraceHull({
|
||
start = self.Owner:GetShootPos(),
|
||
endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200,
|
||
filter = self.Owner,
|
||
mins = Vector(-16, -16, 0),
|
||
maxs = Vector(16, 16, 0),
|
||
mask = MASK_SHOT_HULL,
|
||
})
|
||
end
|
||
|
||
self.Owner:SetAnimation(PLAYER_ATTACK1)
|
||
|
||
----------------------------------------------------------------
|
||
-- ОСОБЫЙ СЛУЧАЙ: постройка sw_sania напрямую (без sent_construction)
|
||
----------------------------------------------------------------
|
||
if SERVER and self.ToBuild == "sw_sania" then
|
||
self:SetNextPrimaryFire(CurTime() + 0.5)
|
||
|
||
if self.Owner:GetNWInt("Scrap") < 300 then
|
||
self.Owner:PrintMessage(HUD_PRINTCENTER, "Not enough scrap (need 300)")
|
||
self.Owner:EmitSound("weapons/wrench_hit_build_fail.wav", 80, 100)
|
||
self.Owner:LagCompensation(false)
|
||
goto do_anim -- чтобы всё равно сыграть анимацию
|
||
end
|
||
|
||
self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") - 300)
|
||
|
||
local hitPos = tr.HitPos
|
||
local hitNormal = tr.HitNormal
|
||
|
||
local placePos = hitPos + hitNormal * 5
|
||
local placeAng = hitNormal:Angle()
|
||
|
||
local reb = ents.Create("sw_sania")
|
||
if IsValid(reb) then
|
||
reb:SetPos(placePos)
|
||
reb:SetAngles(placeAng)
|
||
reb:Spawn()
|
||
reb:Activate()
|
||
|
||
timer.Simple(0, function()
|
||
if not IsValid(reb) then return end
|
||
|
||
local veh
|
||
for _, e in ipairs(ents.FindInSphere(placePos, 200)) do
|
||
if IsValid(e) and (e.LVS or (e.GetBase and IsValid(e:GetBase()) and e:GetBase().LVS)) then
|
||
veh = e.LVS and e or e:GetBase()
|
||
break
|
||
end
|
||
end
|
||
|
||
if IsValid(veh) then
|
||
local vphys = veh:GetPhysicsObject()
|
||
local rphys = reb:GetPhysicsObject()
|
||
|
||
if IsValid(vphys) and IsValid(rphys) then
|
||
constraint.Weld(reb, veh, 0, 0, 0, true, false)
|
||
|
||
timer.Create("REB_AutoRemove_" .. reb:EntIndex(), 1, 0, function()
|
||
if not IsValid(reb) then return end
|
||
if not IsValid(veh) then reb:Remove() end
|
||
end)
|
||
|
||
self.Owner:EmitSound("weapons/building.wav", 80, 120)
|
||
return
|
||
end
|
||
end
|
||
|
||
local phys = reb:GetPhysicsObject()
|
||
if IsValid(phys) then
|
||
phys:EnableMotion(false)
|
||
phys:Sleep()
|
||
end
|
||
|
||
self.Owner:EmitSound("weapons/building.wav", 80, 120)
|
||
end)
|
||
end
|
||
|
||
goto do_anim
|
||
end
|
||
|
||
----------------------------------------------------------------
|
||
-- ПОСТРОЙКА ЧЕРЕЗ sent_construction
|
||
----------------------------------------------------------------
|
||
if SERVER and tr.Hit then
|
||
local entData = Builder_EntitiesTBL[self.ToBuild]
|
||
if entData then
|
||
local existing = ents.FindInSphere(tr.HitPos, 25)
|
||
local hasConstruction = false
|
||
|
||
for _, e in ipairs(existing) do
|
||
if IsValid(e) and e:GetClass() == "sent_construction" then
|
||
hasConstruction = true
|
||
break
|
||
end
|
||
end
|
||
|
||
if hasConstruction then
|
||
self.Owner:EmitSound("weapons/wrench_hit_build_fail.wav", 75, 100)
|
||
else
|
||
local build = ents.Create("sent_construction")
|
||
build:SetPos(tr.HitPos)
|
||
build:SetAngles(Angle(0, self.Owner:EyeAngles().y, 0))
|
||
|
||
build.EntToBuild = entData.Name
|
||
build.EntToBuildCost = entData.cost or 10
|
||
build.EntModel = entData.mdl
|
||
build.EntHealth = entData.health or 10000
|
||
build:SetNick(entData.PrintName)
|
||
build:SetOwned(self.Owner)
|
||
build:SetPlayer(self.Owner)
|
||
|
||
build:Spawn()
|
||
build:Activate()
|
||
|
||
local phys = build:GetPhysicsObject()
|
||
if IsValid(phys) then
|
||
phys:EnableMotion(false)
|
||
phys:Sleep()
|
||
end
|
||
|
||
self.Owner:EmitSound("weapons/building.wav", 80, 120)
|
||
end
|
||
end
|
||
end
|
||
|
||
----------------------------------------------------------------
|
||
-- УДАР ПО УЖЕ СУЩЕСТВУЮЩЕМУ ОБЪЕКТУ
|
||
----------------------------------------------------------------
|
||
if SERVER and (IsValid(tr.Entity) or tr.Entity == Entity(0)) then
|
||
local v = tr.Entity
|
||
local entClass = v:GetClass()
|
||
|
||
-- 1. Пытаемся понять, это ли наша постройка из Builder_EntitiesTBL
|
||
local is_built_entity = false
|
||
local entity_data = nil
|
||
|
||
if IsValid(v) then
|
||
for _, entData in pairs(Builder_EntitiesTBL) do
|
||
local is_match = false
|
||
|
||
-- обычные энтити (turrets, mines и т.п.)
|
||
if entData.Name == entClass then
|
||
is_match = true
|
||
-- наши фортификации, которые после строительства становятся constructor_prop с нужной моделью
|
||
elseif entClass == "constructor_prop" and entData.mdl == v:GetModel() then
|
||
is_match = true
|
||
-- NPC из таблицы
|
||
elseif list.Get("NPC")[entData.Name] and entData.Name == entClass then
|
||
is_match = true
|
||
end
|
||
|
||
if is_match then
|
||
is_built_entity = true
|
||
entity_data = entData
|
||
break
|
||
end
|
||
end
|
||
end
|
||
|
||
-- 2. Если это наша постройка — ломаем её и возвращаем половину цены
|
||
if is_built_entity and entity_data and entClass ~= "sent_construction" then
|
||
if not v.BuilderHealth then
|
||
v.BuilderHealth = entity_data.health or 500
|
||
v.BuilderMaxHealth = v.BuilderHealth
|
||
v.BuilderScrapValue = (entity_data.cost or 0) * 5 -- полная цена в скрапе
|
||
end
|
||
|
||
v.BuilderHealth = v.BuilderHealth - self.Primary.Damage
|
||
local hp = math.max(v.BuilderHealth, 0)
|
||
|
||
self.Owner:PrintMessage(
|
||
HUD_PRINTCENTER,
|
||
"Ломаю постройку (" .. hp .. " HP)"
|
||
)
|
||
|
||
self.Owner:EmitSound("weapons/cbar_hit2.wav", 80, 100)
|
||
|
||
net.Start("builder_hitprop")
|
||
net.WriteVector(tr.HitPos)
|
||
net.WriteInt(hp, 16)
|
||
net.Send(self.Owner)
|
||
|
||
if v.BuilderHealth <= 0 then
|
||
local full_cost = v.BuilderScrapValue or ((entity_data.cost or 0) * 5)
|
||
local refund = math.floor(full_cost / 2)
|
||
|
||
if refund > 0 then
|
||
self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") + refund)
|
||
end
|
||
|
||
v:Remove()
|
||
end
|
||
else
|
||
-- СТАРОЕ ПОВЕДЕНИЕ: урон/ремонт всего остального
|
||
if entClass ~= "sent_construction" then
|
||
if BuilderIsCreature(v) or v:Health() <= 0 or v:GetMaxHealth() <= 0 then
|
||
local dmginfo = DamageInfo()
|
||
dmginfo:SetAttacker(self.Owner)
|
||
dmginfo:SetInflictor(self)
|
||
dmginfo:SetDamage(self.Primary.Damage)
|
||
dmginfo:SetDamageType(DMG_CLUB)
|
||
dmginfo:SetDamagePosition(self.Owner:GetShootPos())
|
||
dmginfo:SetDamageForce(self.Owner:EyeAngles():Forward() * self.Primary.Damage * self.Primary.Force)
|
||
SuppressHostEvents(NULL)
|
||
v:DispatchTraceAttack(dmginfo, tr)
|
||
SuppressHostEvents(self.Owner)
|
||
|
||
if v ~= Entity(0) and (not BuilderIsCreature(v) or not (v:GetClass():find("manhack") or v:GetClass():find("scanner") or v:GetClass():find("robot") or v:GetClass():find("turret") or v:GetClass():find("roller") or v:GetClass():find("sentry"))) then
|
||
self.Owner:EmitSound(")weapons/cbar_hitbod" .. math.random(1, 3) .. ".wav", 80, math.random(95, 100))
|
||
else
|
||
self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 75, math.random(95, 100))
|
||
end
|
||
else
|
||
if v:GetMaxHealth() * 10 <= v:Health() then
|
||
self.Owner:EmitSound("weapons/wrench_hit_build_fail.wav", 80, math.random(95, 100))
|
||
elseif self.Owner:GetNWInt("Scrap") < 5 then
|
||
self.Owner:PrintMessage(HUD_PRINTCENTER, "Not enough scrap")
|
||
self.Owner:EmitSound("weapons/wrench_hit_build_fail.wav", 80, math.random(95, 100))
|
||
else
|
||
v:SetHealth(math.min(v:GetMaxHealth() * 10, v:Health() + 5))
|
||
self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") - 5)
|
||
self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 75, math.random(95, 100))
|
||
end
|
||
end
|
||
end
|
||
|
||
if entClass == "sent_construction" and self.Owner:GetNWInt("Scrap") >= 5 then
|
||
v:SetProgress(math.Round(v:GetProgress() + 100 / v.EntToBuildCost, 2))
|
||
self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") - 5)
|
||
self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 80, math.random(95, 100))
|
||
|
||
local pos = tr.HitPos
|
||
timer.Simple(0, function()
|
||
local ef = EffectData()
|
||
ef:SetOrigin(pos)
|
||
util.Effect("builderef1", ef)
|
||
end)
|
||
elseif entClass == "sent_construction" then
|
||
self.Owner:PrintMessage(HUD_PRINTCENTER, "Not enough scrap")
|
||
self.Owner:EmitSound("weapons/wrench_hit_build_fail.wav", 80, math.random(95, 100))
|
||
end
|
||
end
|
||
|
||
self.cooldwn = CurTime() + 2
|
||
timer.Create("swing" .. self:EntIndex(), 2, 1, function() end)
|
||
else
|
||
self.cooldwn = CurTime() + 1
|
||
end
|
||
|
||
::do_anim::
|
||
|
||
if tr.Hit then
|
||
self.Owner:ViewPunch(Angle(-1, -1, 0))
|
||
self.Weapon:SendWeaponAnim(ACT_VM_HITCENTER)
|
||
self:SetNextPrimaryFire(CurTime() + self.Primary.Delay)
|
||
self:SetNextSecondaryFire(CurTime() + self.Primary.Delay)
|
||
else
|
||
self.Owner:ViewPunch(Angle(1, 1, 0))
|
||
self:EmitSound(")weapons/wrench_swing.wav", 80, math.random(95, 105))
|
||
self.Weapon:SendWeaponAnim(ACT_VM_MISSCENTER)
|
||
self:SetNextPrimaryFire(CurTime() + self.Primary.Delay)
|
||
self:SetNextSecondaryFire(CurTime() + self.Primary.Delay)
|
||
end
|
||
|
||
self.Idle = 0
|
||
self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration()
|
||
self.Owner:LagCompensation(false)
|
||
end
|
||
|
||
function SWEP:SecondaryAttack()
|
||
self.Owner:LagCompensation(true)
|
||
|
||
if CLIENT then
|
||
local tr = util.TraceLine({
|
||
start = self.Owner:GetShootPos(),
|
||
endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200,
|
||
filter = self.Owner,
|
||
mask = MASK_SHOT_HULL,
|
||
})
|
||
|
||
if not IsValid(tr.Entity) then
|
||
tr = util.TraceHull({
|
||
start = self.Owner:GetShootPos(),
|
||
endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200,
|
||
filter = self.Owner,
|
||
mins = Vector(-16, -16, 0),
|
||
maxs = Vector(16, 16, 0),
|
||
mask = MASK_SHOT_HULL,
|
||
})
|
||
end
|
||
|
||
if tr.Hit then
|
||
local ef = EffectData()
|
||
ef:SetOrigin(tr.HitPos)
|
||
util.Effect("cball_bounce", ef)
|
||
end
|
||
end
|
||
|
||
if SERVER then
|
||
local ply = self.Owner
|
||
local eyePos = ply:GetShootPos()
|
||
local eyeDir = ply:GetAimVector()
|
||
|
||
local bestEnt
|
||
local bestDist = 200
|
||
|
||
for _, ent in ipairs(ents.FindByClass("sw_sania")) do
|
||
if not IsValid(ent) then continue end
|
||
|
||
local toEnt = ent:GetPos() - eyePos
|
||
local dist = toEnt:Length()
|
||
if dist > 200 then continue end
|
||
|
||
local dot = eyeDir:Dot(toEnt:GetNormalized())
|
||
if dot < 0.8 then continue end
|
||
|
||
if dist < bestDist then
|
||
bestDist = dist
|
||
bestEnt = ent
|
||
end
|
||
end
|
||
|
||
if IsValid(bestEnt) then
|
||
ply:SetNWInt("Scrap", ply:GetNWInt("Scrap") + 150)
|
||
|
||
bestEnt:Remove()
|
||
ply:EmitSound("buttons/button19.wav", 75, 90)
|
||
ply:LagCompensation(false)
|
||
return
|
||
end
|
||
end
|
||
|
||
local tr = util.TraceLine({
|
||
start = self.Owner:GetShootPos(),
|
||
endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200, -- Дистанция постройки
|
||
filter = self.Owner,
|
||
mask = MASK_SHOT_HULL,
|
||
})
|
||
|
||
if not IsValid(tr.Entity) then
|
||
tr = util.TraceHull({
|
||
start = self.Owner:GetShootPos(),
|
||
endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 200, -- Дистанция постройки
|
||
filter = self.Owner,
|
||
mins = Vector(-16, -16, 0),
|
||
maxs = Vector(16, 16, 0),
|
||
mask = MASK_SHOT_HULL,
|
||
})
|
||
end
|
||
|
||
self.Owner:SetAnimation(PLAYER_ATTACK1)
|
||
|
||
if SERVER and (IsValid(tr.Entity) or tr.Entity == Entity(0)) then
|
||
local v = tr.Entity
|
||
local entClass = v:GetClass()
|
||
|
||
local is_built_entity = false
|
||
local entity_data = nil
|
||
|
||
for k, entData in pairs(Builder_EntitiesTBL) do
|
||
if entData.Name == entClass then
|
||
is_built_entity = true
|
||
entity_data = entData
|
||
break
|
||
elseif entClass == "constructor_prop" and entData.mdl == v:GetModel() then
|
||
is_built_entity = true
|
||
entity_data = entData
|
||
break
|
||
end
|
||
end
|
||
|
||
-- если это наша постройка — ломаем и возвращаем половину стоимости
|
||
if is_built_entity and entClass ~= "sent_construction" then
|
||
|
||
-- инициализация данных постройки (один раз)
|
||
if not v.BuilderHealth then
|
||
v.BuilderHealth = entity_data.health or 500
|
||
v.BuilderMaxHealth = v.BuilderHealth
|
||
|
||
local full_cost = (entity_data.cost or 0) * 5
|
||
v.BuilderScrapValue = math.floor(full_cost / 2) -- половина цены
|
||
end
|
||
|
||
-- наносим урон
|
||
v.BuilderHealth = v.BuilderHealth - self.Primary.Damage
|
||
local hp = math.max(v.BuilderHealth, 0)
|
||
|
||
self.Owner:PrintMessage(HUD_PRINTCENTER, "Ломаю постройку (" .. hp .. " HP)")
|
||
self.Owner:EmitSound("weapons/cbar_hit2.wav", 80, 100)
|
||
|
||
net.Start("builder_hitprop")
|
||
net.WriteVector(tr.HitPos)
|
||
net.WriteInt(hp, 16)
|
||
net.Send(self.Owner)
|
||
|
||
-- уничтожение и возврат
|
||
if v.BuilderHealth <= 0 then
|
||
local refund = v.BuilderScrapValue or 0
|
||
self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") + refund)
|
||
v:Remove()
|
||
end
|
||
|
||
return -- ВАЖНО: чтобы не выполнялся старый код ниже
|
||
end
|
||
|
||
if entClass == "sent_construction" then
|
||
local spent_scrap = math.Round(v:GetProgress() / 100 * v.EntToBuildCost * 5)
|
||
self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") + spent_scrap)
|
||
v:Remove()
|
||
self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 75, math.random(95, 100))
|
||
else
|
||
local is_built_entity = false
|
||
local entity_data = nil
|
||
|
||
for k, entData in pairs(Builder_EntitiesTBL) do
|
||
local is_match = false
|
||
|
||
if entData.Name == entClass then
|
||
is_match = true
|
||
elseif entClass == "constructor_prop" and entData.mdl == v:GetModel() then
|
||
is_match = true
|
||
elseif list.Get("NPC")[entData.Name] and entData.Name == entClass then
|
||
is_match = true
|
||
end
|
||
|
||
if is_match then
|
||
is_built_entity = true
|
||
entity_data = entData
|
||
break
|
||
end
|
||
end
|
||
|
||
if is_built_entity and entity_data then
|
||
if not v.BuilderHealth then
|
||
v.BuilderHealth = entity_data.health or 500
|
||
v.BuilderMaxHealth = v.BuilderHealth
|
||
|
||
-- сразу считаем ВОЗВРАТ (половина цены из таблицы, с учётом *5)
|
||
local full_cost = (entity_data.cost or 0) * 5
|
||
v.BuilderScrapValue = math.floor(full_cost / 2)
|
||
end
|
||
|
||
v.BuilderHealth = v.BuilderHealth - self.Primary.Damage
|
||
local hp = math.max(v.BuilderHealth, 0)
|
||
|
||
if SERVER then
|
||
self.Owner:PrintMessage(
|
||
HUD_PRINTCENTER,
|
||
"Ломаю постройку (" .. hp .. " HP)"
|
||
)
|
||
|
||
self.Owner:EmitSound("weapons/cbar_hit2.wav", 80, 100)
|
||
|
||
net.Start("builder_hitprop")
|
||
net.WriteVector(tr.HitPos)
|
||
net.WriteInt(hp, 16)
|
||
net.Send(self.Owner)
|
||
|
||
if v.BuilderHealth <= 0 then
|
||
local refund = v.BuilderScrapValue or 0
|
||
self.Owner:SetNWInt("Scrap", self.Owner:GetNWInt("Scrap") + refund)
|
||
v:Remove()
|
||
end
|
||
end
|
||
else
|
||
local dmginfo = DamageInfo()
|
||
dmginfo:SetAttacker(self.Owner)
|
||
dmginfo:SetInflictor(self)
|
||
dmginfo:SetDamage(self.Primary.Damage)
|
||
dmginfo:SetDamageType(DMG_CLUB)
|
||
dmginfo:SetDamagePosition(self.Owner:GetShootPos())
|
||
dmginfo:SetDamageForce(self.Owner:EyeAngles():Forward() * self.Primary.Damage * self.Primary.Force)
|
||
SuppressHostEvents(NULL)
|
||
v:DispatchTraceAttack(dmginfo, tr)
|
||
SuppressHostEvents(self.Owner)
|
||
|
||
if BuilderIsCreature(v) then
|
||
if not (v:GetClass():find("manhack") or v:GetClass():find("scanner") or v:GetClass():find("robot") or v:GetClass():find("turret") or v:GetClass():find("roller") or v:GetClass():find("sentry")) then
|
||
self.Owner:EmitSound(")weapons/cbar_hitbod" .. math.random(1, 3) .. ".wav", 80, math.random(95, 100))
|
||
else
|
||
self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 75, math.random(95, 100))
|
||
end
|
||
else
|
||
self.Owner:EmitSound(")weapons/cbar_hit" .. math.random(1, 2) .. ".wav", 75, math.random(95, 100))
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
if tr.Hit then
|
||
self.Owner:ViewPunch(Angle(-1, -1, 0))
|
||
self.Weapon:SendWeaponAnim(ACT_VM_HITCENTER)
|
||
self:SetNextPrimaryFire(CurTime() + self.Primary.Delay)
|
||
self:SetNextSecondaryFire(CurTime() + self.Primary.Delay)
|
||
else
|
||
self.Owner:ViewPunch(Angle(1, 1, 0))
|
||
self:EmitSound(")weapons/wrench_swing.wav", 80, math.random(95, 105))
|
||
self.Weapon:SendWeaponAnim(ACT_VM_MISSCENTER)
|
||
self:SetNextPrimaryFire(CurTime() + self.Primary.Delay)
|
||
self:SetNextSecondaryFire(CurTime() + self.Primary.Delay)
|
||
end
|
||
|
||
self.Idle = 0
|
||
self.IdleTimer = CurTime() + self.Owner:GetViewModel():SequenceDuration()
|
||
self.Owner:LagCompensation(false)
|
||
end
|
||
|
||
if CLIENT then
|
||
net.Receive("sendToBuilder", function()
|
||
local wep = LocalPlayer():GetActiveWeapon()
|
||
if IsValid(wep) then
|
||
if IsValid(wep.Ghost) then
|
||
wep.Ghost:Remove()
|
||
end
|
||
wep.Ghost = nil
|
||
end
|
||
end)
|
||
|
||
net.Receive("builder_hitprop", function()
|
||
local hitPos = net.ReadVector()
|
||
local hp = net.ReadInt(16)
|
||
|
||
local ef = EffectData()
|
||
ef:SetOrigin(hitPos)
|
||
util.Effect("cball_bounce", ef)
|
||
|
||
local spark = EffectData()
|
||
spark:SetOrigin(hitPos)
|
||
util.Effect("ManhackSparks", spark)
|
||
|
||
-- чат‑индикатор
|
||
chat.AddText(
|
||
Color(255, 200, 50), "[Молоток] ",
|
||
color_white, "Урон по постройке (" .. hp .. " HP)"
|
||
)
|
||
end)
|
||
end
|
||
|
||
if CLIENT then
|
||
net.Receive("builderMenu", function()
|
||
local ply = LocalPlayer()
|
||
local wep = ply:GetActiveWeapon()
|
||
if IsValid(wep) and wep:GetClass() == "weapon_builder" then
|
||
wep:Reload()
|
||
end
|
||
end)
|
||
end
|
||
|
||
function SWEP:Reload()
|
||
if not self:IsValid() or not Builder_EntitiesLoaded then return false end
|
||
if CurTime() > nextreload then
|
||
nextreload = CurTime() + 1
|
||
|
||
if SERVER then
|
||
net.Start("builderMenu")
|
||
net.Send(self.Owner)
|
||
return
|
||
end
|
||
|
||
local new = false
|
||
if not IsValid(buildermenu) then
|
||
new = true
|
||
buildermenu = vgui.Create("DFrame")
|
||
buildermenu:SetSize(ScrW() / 2.2, ScrH() / 1.5)
|
||
buildermenu:SetTitle("")
|
||
buildermenu:SetIcon("icon16/script_gear.png")
|
||
buildermenu:SetDraggable(false)
|
||
buildermenu:SetAlpha(1)
|
||
buildermenu:ShowCloseButton(false)
|
||
end
|
||
|
||
local frame = buildermenu
|
||
frame:AlphaTo(255, 0.25)
|
||
frame:MakePopup()
|
||
frame:Center()
|
||
frame:Show()
|
||
frame.B_Close = false
|
||
frame:SetKeyboardInputEnabled(true)
|
||
frame:SetMouseInputEnabled(true)
|
||
frame.N_Scrap = 0
|
||
|
||
if not new then return end
|
||
|
||
function frame:Paint(w, h)
|
||
if IsValid(LocalPlayer()) then
|
||
frame.N_Scrap = LocalPlayer():GetNWInt("Scrap")
|
||
end
|
||
|
||
Derma_DrawBackgroundBlur(self, self.m_fCreateTime)
|
||
draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 200))
|
||
surface.SetDrawColor(255, 255, 255, (150 + math.sin(RealTime() * 5.2) * 100) * .8)
|
||
surface.DrawOutlinedRect(0, 0, w, h)
|
||
surface.DrawOutlinedRect(0, 0, w, 25)
|
||
|
||
draw.TextShadow({
|
||
text = "Молоток [Металлолом:" .. frame.N_Scrap .. "]",
|
||
pos = {26, 12},
|
||
font = "TargetID",
|
||
xalign = TEXT_ALIGN_LEFT,
|
||
yalign = TEXT_ALIGN_CENTER,
|
||
color = Color(255, 255, 255)
|
||
}, 1, 255)
|
||
end
|
||
|
||
function frame:DoClose()
|
||
if frame.B_Close then return end
|
||
frame.B_Close = true
|
||
frame:AlphaTo(1, 0.25)
|
||
frame:SetKeyboardInputEnabled(false)
|
||
frame:SetMouseInputEnabled(false)
|
||
timer.Simple(0.25, function()
|
||
if IsValid(frame) and frame.B_Close then frame:Hide() end
|
||
end)
|
||
end
|
||
|
||
local CloseButton = frame:Add("DButton")
|
||
local pax = CloseButton
|
||
pax:SetText("")
|
||
pax:SetPos(810, 3)
|
||
pax:SetSize(60, 18)
|
||
pax.B_Hover = false
|
||
|
||
function pax:Paint(w, h)
|
||
draw.TextShadow({
|
||
text = "Close",
|
||
pos = {w / 2, h / 2},
|
||
font = "TargetID",
|
||
xalign = TEXT_ALIGN_CENTER,
|
||
yalign = TEXT_ALIGN_CENTER,
|
||
color = (pax.B_Hover and Color(255, 0, 0) or Color(255, 255, 255))
|
||
}, 1, 255)
|
||
end
|
||
|
||
function pax:DoClick()
|
||
frame:DoClose()
|
||
end
|
||
|
||
function pax:OnCursorEntered()
|
||
pax.B_Hover = true
|
||
end
|
||
|
||
function pax:OnCursorExited()
|
||
pax.B_Hover = false
|
||
end
|
||
|
||
local PropPanel = vgui.Create("ContentContainer", frame)
|
||
PropPanel:SetTriggerSpawnlistChange(false)
|
||
PropPanel:Dock(FILL)
|
||
|
||
local Categorised = {}
|
||
Categorised["Фортификации (" .. Builder_EntitiesNum .. ")"] = Builder_EntitiesTBL
|
||
|
||
for CategoryName, v in SortedPairs(Categorised) do
|
||
local Header = vgui.Create("ContentHeader", PropPanel)
|
||
Header:SetText(CategoryName)
|
||
PropPanel:Add(Header)
|
||
|
||
for k, WeaponTable in SortedPairsByMemberValue(v, "Name") do
|
||
if WeaponTable.PrintName ~= nil then
|
||
local icon = vgui.Create("ContentIcon", PropPanel)
|
||
icon.ClassName = k
|
||
|
||
local iconpath = "entities/" .. WeaponTable.Name .. ".png"
|
||
if not file.Exists("materials/" .. iconpath, "game") then
|
||
iconpath = "vgui/entities/" .. WeaponTable.Name .. ".vmt"
|
||
end
|
||
|
||
icon:SetMaterial(iconpath)
|
||
icon:SetName(WeaponTable.PrintName)
|
||
icon:SetToolTip("Cost: " .. (WeaponTable.cost and WeaponTable.cost * 5 or "???"))
|
||
|
||
icon.DoClick = function()
|
||
frame:DoClose()
|
||
net.Start("sendToBuilder")
|
||
net.WriteString(icon.ClassName)
|
||
net.SendToServer()
|
||
end
|
||
|
||
PropPanel:Add(icon)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
if CLIENT then
|
||
surface.CreateFont("ScrapFont", {
|
||
font = "Digital-7",
|
||
size = 50,
|
||
weight = 400,
|
||
scanlines = true,
|
||
antialias = true
|
||
})
|
||
|
||
surface.CreateFont("ScrapFont2", {
|
||
font = "Digital-7",
|
||
size = 36,
|
||
weight = 400,
|
||
scanlines = true,
|
||
antialias = true
|
||
})
|
||
|
||
local scrap = Material("icon/refined_metal.png")
|
||
|
||
hook.Add("HUDPaint", "BuilderHud", function()
|
||
local fade = LocalPlayer().UIScrapFade or 0
|
||
local negative = 0
|
||
local w = 50
|
||
local h = ScrH() / 3
|
||
local wep = LocalPlayer():GetActiveWeapon()
|
||
|
||
if IsValid(wep) and wep:GetClass() == "weapon_builder" then
|
||
fade = 200
|
||
end
|
||
|
||
if fade > negative then
|
||
local alpha = math.Clamp(fade, negative, 100) / 100
|
||
if fade > negative then
|
||
LocalPlayer().UIScrapFade = math.Clamp(fade, negative, 400) - 1
|
||
w = math.Clamp(fade, negative, 100) / 2
|
||
end
|
||
|
||
surface.SetDrawColor(255, 200, 25, (150 + math.sin(RealTime() * 5.2) * 100) * .8 * alpha)
|
||
surface.DrawOutlinedRect(w, h, 128, 128)
|
||
|
||
surface.SetDrawColor(0, 0, 0, 128 * alpha)
|
||
surface.DrawRect(w, h, 128, 128)
|
||
|
||
surface.SetDrawColor(255, 255, 255, 255 * alpha)
|
||
surface.SetMaterial(scrap)
|
||
surface.DrawTexturedRect(w, h - 20, 128, 128)
|
||
|
||
draw.SimpleText(
|
||
tostring(math.Clamp(LocalPlayer():GetNWInt("Scrap", 0), 0, 99999)),
|
||
"ScrapFont",
|
||
w + 65,
|
||
h + 78,
|
||
Color(255, 200, 25, 255 * alpha),
|
||
1,
|
||
0
|
||
)
|
||
end
|
||
end)
|
||
end
|
||
if CLIENT then
|
||
local Mat = Material("sprites/light_ignorez")
|
||
|
||
if true then
|
||
local nam = "builderef1"
|
||
local EFFECT = {}
|
||
|
||
function EFFECT:Init(data)
|
||
local pos = data:GetOrigin()
|
||
self:SetRenderBounds(-Vector(32, 32, 32), Vector(32, 32, 32))
|
||
self.Emitter = ParticleEmitter(pos)
|
||
|
||
for i = 1, math.random(8, 16) do
|
||
local particle = self.Emitter:Add("effects/spark", pos)
|
||
particle:SetVelocity(VectorRand():GetNormalized() * math.random(48, 96))
|
||
particle:SetLifeTime(0)
|
||
particle:SetDieTime(0.5)
|
||
local Siz = math.Rand(1, 3)
|
||
particle:SetStartSize(Siz)
|
||
particle:SetEndSize(0)
|
||
particle:SetStartLength(Siz * 2)
|
||
particle:SetEndLength(Siz)
|
||
particle:SetStartAlpha(128)
|
||
particle:SetEndAlpha(0)
|
||
particle:SetColor(255, 255, 128)
|
||
particle:SetLighting(false)
|
||
particle:SetCollide(true)
|
||
particle:SetGravity(Vector(0, 0, -128))
|
||
particle:SetBounce(1)
|
||
end
|
||
|
||
for i = 1, 4 do
|
||
local particle = self.Emitter:Add("particle/particle_smokegrenade1", pos)
|
||
if particle then
|
||
particle:SetVelocity(VectorRand():GetNormalized() * math.random(32, 64))
|
||
particle:SetLifeTime(0)
|
||
particle:SetDieTime(math.Rand(0.5, 1))
|
||
particle:SetStartAlpha(32)
|
||
particle:SetEndAlpha(0)
|
||
local Siz = math.Rand(12, 24)
|
||
particle:SetStartSize(Siz / 4)
|
||
particle:SetEndSize(Siz)
|
||
particle:SetRoll(math.random(0, 360))
|
||
particle:SetColor(128, 128, 128)
|
||
particle:SetGravity(Vector(0, 0, math.random(32, 64)))
|
||
particle:SetAirResistance(256)
|
||
particle:SetCollide(true)
|
||
particle:SetBounce(1)
|
||
end
|
||
end
|
||
|
||
local dlight = DynamicLight(0)
|
||
if dlight then
|
||
dlight.pos = pos
|
||
dlight.r = 255
|
||
dlight.g = 255
|
||
dlight.b = 128
|
||
dlight.brightness = 1
|
||
dlight.decay = 128
|
||
dlight.size = 64
|
||
dlight.dietime = CurTime() + 0.5
|
||
end
|
||
|
||
for i = 1, math.random(1, 4) do
|
||
local ef = EffectData()
|
||
ef:SetOrigin(pos)
|
||
util.Effect("builderef2", ef)
|
||
end
|
||
end
|
||
|
||
function EFFECT:Think() return false end
|
||
function EFFECT:Render() end
|
||
|
||
effects.Register(EFFECT, nam)
|
||
end
|
||
|
||
if true then
|
||
local nam = "builderef2"
|
||
local EFFECT = {}
|
||
|
||
function EFFECT:Init(data)
|
||
self.Entity:SetModel("models/gibs/metal_gib" .. math.random(1, 5) .. ".mdl")
|
||
self.Entity:PhysicsInit(SOLID_VPHYSICS)
|
||
self.Entity:SetRenderMode(RENDERMODE_TRANSCOLOR)
|
||
self.Entity:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
|
||
self.Entity:SetModelScale(math.Rand(0.25, 0.5))
|
||
self.Entity:SetRenderMode(RENDERMODE_TRANSCOLOR)
|
||
|
||
local phys = self.Entity:GetPhysicsObject()
|
||
if IsValid(phys) then
|
||
phys:Wake()
|
||
phys:EnableMotion(true)
|
||
phys:SetMaterial("gmod_silent")
|
||
phys:SetAngles(Angle(math.Rand(0, 360), math.Rand(0, 360), math.Rand(0, 360)))
|
||
local vel = VectorRand():GetNormalized() * math.Rand(64, 128)
|
||
vel = Vector(vel.x, vel.y, math.abs(vel.z))
|
||
phys:SetVelocity(vel)
|
||
phys:Wake()
|
||
end
|
||
|
||
self.LifeTime = CurTime() + math.Rand(0.5, 1)
|
||
self.LifeAlp = 255
|
||
end
|
||
|
||
function EFFECT:PhysicsCollide(data, physobj) end
|
||
|
||
function EFFECT:Think()
|
||
if self.LifeTime < CurTime() then
|
||
self.LifeAlp = Lerp(0.05, self.LifeAlp, 0)
|
||
self.Entity:SetColor(Color(255, 255, 255, self.LifeAlp))
|
||
if self.LifeAlp <= 1 then return false end
|
||
end
|
||
return true
|
||
end
|
||
|
||
function EFFECT:Render()
|
||
self.Entity:DrawModel()
|
||
end
|
||
|
||
effects.Register(EFFECT, nam)
|
||
end
|
||
end
|
||
|
||
if SERVER then
|
||
util.AddNetworkString("sendToBuilder")
|
||
util.AddNetworkString("builderMenu")
|
||
util.AddNetworkString("builder_sync")
|
||
|
||
net.Receive("sendToBuilder", function(len, ply)
|
||
local cls = net.ReadString()
|
||
if not istable(Builder_EntitiesTBL[cls]) then return end
|
||
|
||
local weapon = ply:GetActiveWeapon()
|
||
if IsValid(weapon) and weapon:GetClass() == "weapon_builder" then
|
||
local tab = Builder_EntitiesTBL[cls]
|
||
|
||
weapon.ToBuildCost = tab.cost or 10
|
||
weapon.ToBuild = cls
|
||
weapon.ToBuildmdl = tab.mdl
|
||
weapon.ToBuildname = tab.PrintName
|
||
|
||
if IsValid(weapon.buildEnt) then
|
||
weapon.buildEnt:Remove()
|
||
weapon.buildEnt = nil
|
||
end
|
||
|
||
net.Start("builder_sync")
|
||
net.WriteString(cls)
|
||
net.Send(ply)
|
||
end
|
||
end)
|
||
end
|
||
|
||
if CLIENT then
|
||
net.Receive("builder_sync", function()
|
||
local cls = net.ReadString()
|
||
local wep = LocalPlayer():GetActiveWeapon()
|
||
if not IsValid(wep) or wep:GetClass() ~= "weapon_builder" then return end
|
||
|
||
local tab = Builder_EntitiesTBL[cls]
|
||
if not tab then return end
|
||
|
||
wep.ToBuild = cls
|
||
wep.ToBuildmdl = tab.mdl
|
||
wep.ToBuildname = tab.PrintName
|
||
wep.ToBuildCost = tab.cost or 10
|
||
|
||
if IsValid(wep.Ghost) then
|
||
wep.Ghost:Remove()
|
||
end
|
||
wep.Ghost = nil
|
||
end)
|
||
end
|
||
|
||
|
||
hook.Add("PlayerSpawn", "BuilderForceResetScrap", function(ply)
|
||
ply:SetNWInt("Scrap", 500)
|
||
end)
|
||
|
||
if CLIENT then
|
||
local EFFECT = {}
|
||
|
||
function EFFECT:Init(data)
|
||
local pos = data:GetOrigin()
|
||
|
||
self:SetRenderBounds(-Vector(32, 32, 32), Vector(32, 32, 32))
|
||
self.Emitter = ParticleEmitter(pos)
|
||
|
||
-- искры
|
||
for i = 1, math.random(8, 16) do
|
||
local particle = self.Emitter:Add("effects/spark", pos)
|
||
if not particle then continue end
|
||
|
||
particle:SetVelocity(VectorRand():GetNormalized() * math.random(48, 96))
|
||
particle:SetLifeTime(0)
|
||
particle:SetDieTime(0.5)
|
||
|
||
local siz = math.Rand(1, 3)
|
||
particle:SetStartSize(siz)
|
||
particle:SetEndSize(0)
|
||
particle:SetStartLength(siz * 2)
|
||
particle:SetEndLength(siz)
|
||
|
||
particle:SetStartAlpha(255)
|
||
particle:SetEndAlpha(0)
|
||
|
||
particle:SetColor(255, 255, 128)
|
||
particle:SetLighting(false)
|
||
particle:SetCollide(true)
|
||
particle:SetGravity(Vector(0, 0, -128))
|
||
particle:SetBounce(1)
|
||
end
|
||
|
||
if self.Emitter then
|
||
self.Emitter:Finish()
|
||
end
|
||
end
|
||
|
||
function EFFECT:Think()
|
||
return false
|
||
end
|
||
|
||
function EFFECT:Render()
|
||
end
|
||
|
||
effects.Register(EFFECT, "builderef1")
|
||
end
|
||
|
||
hook.Add("PlayerDeath", "BuilderCancelConstructionOnDeath", function(ply)
|
||
for _, ent in ipairs(ents.FindByClass("sent_construction")) do
|
||
if ent:GetOwned() == ply or ent:GetPlayer() == ply then
|
||
ent:Remove()
|
||
end
|
||
end
|
||
end)
|
||
|
||
hook.Add("OnNPCKilled", "ScrapDrop", function() end)
|
||
hook.Add("PlayerDeath", "ScrapDrop", function() end)
|