Files
wnsrc/lua/entities/prop_vehicle_zapc/cl_init.lua

514 lines
19 KiB
Lua
Raw Normal View History

2024-08-04 22:55:00 +03:00
--[[
| 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/
--]]
-- ZAPC
-- Copyright (c) 2012 Zaubermuffin
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-- Local > global!
local Color = Color
local surface = surface
local draw = draw
local LocalPlayer = LocalPlayer
local math = math
local IsValid = IsValid
local util = util
local render = render
local TimedSin = TimedSin
local Lerp = Lerp
include('shared.lua')
-- CC
local ZAPC_VIEW_UNRELATED, ZAPC_VIEW_DRIVER, ZAPC_VIEW_GUNNER, ZAPC_VIEW_PASSENGER = ZAPC_VIEW_UNRELATED, ZAPC_VIEW_DRIVER, ZAPC_VIEW_GUNNER, ZAPC_VIEW_PASSENGER
local ZAPC_MAX_HEALTH = ZAPC_MAX_HEALTH
local ZAPC_PRIMARY_RELOAD_TIME, ZAPC_SECONDARY_RELOAD_TIME = ZAPC_PRIMARY_RELOAD_TIME, ZAPC_SECONDARY_RELOAD_TIME
local ZAPC_MAX_PASSENGERS = ZAPC_MAX_PASSENGERS
local ZAPC_HUD_OKAY = Color(8, 91, 181, 255) --Color(44, 110, 235, 255) -- full health
local ZAPC_HUD_OKAY_BG = Color(149, 214, 243, 80)
local ZAPC_HUD_NOT_OKAY = Color(227, 0, 0, 255) -- absolutely dead
local ZAPC_HUD_NOT_OKAY_BG = Color(120, 0, 0, 120)
local ZAPC_HUD_HEALTH_FLASH_TIME = 0.5 -- seconds the interface flashes red after you were hit.
local ZAPC_HUD_BULLET_WARNING_FLASH_FREQ = 1.5 -- the smaller the slower.
local ZAPC_MIN_ZOOM = 75 -- FOV when unzoomed
local ZAPC_MAX_ZOOM = 25 -- FOV when zoomed
local ZAPC_ZOOM_RATE = 2.5 -- FOV/Tick
-- Get rid of them.
for k, v in pairs(_G) do
if type(k) == 'string' and k:find('^ZAPC_') then
_G[k] = nil
end
end
-- Are we somehow related to the APC?
local ViewMode = 0
-- The APC
local ZAPC = nil
-- Driver, gunner, passengers
local Driver, Gunner, Passengers = nil, nil, {}
-- Bullets we have and the last reload actions
local Bullets, LastPrimary, LastSecondary, NextPrimary, NextSecondary = 0, 0, 0, 0, 0
-- Health of the APC
local Health = 0
-- Last time we were hit by something.
local LastHealthLoss = 0
-- OH GOD
local Destructing = false
-- Hatch
local HatchOpened = false
-- The last velocity we had - to avoid that the user interface is whopping around too badly, we're cheating a little.
local lastVel = 0
-- Our "zoom" status.
local Zoom = 75
-- The viewmode. This used to be gmod_vehicle_viewmode but got dropped in a "recent" update.
-- Since we still want this to be possible, I'll re-add it.
local viewmode = CreateClientConVar('zapc_viewmode', 1, true, false)
-- Create the fonts.
-- For whatever reason Garry decided it was a sweet idea to make Trebuchet24/18 additive in GM13. :|
surface.CreateFont("ZAPC_HUD",
{
font = "Trebuchet MS",
size = 24,
antialias = true,
weight = 600
})
surface.CreateFont("ZAPC_HUDSmall",
{
font = "Trebuchet MS",
size = 18,
antialias = true,
weight = 600
})
local function LimitPitch(ang)
return math.Clamp(ang, -45, 45)
end
local function ProcessAngle(ang)
local apcAngles = ZAPC:GetAngles()
ang.pitch = LimitPitch(math.NormalizeAngle(ang.pitch - apcAngles.pitch))
ang.yaw = math.NormalizeAngle(ang.yaw + apcAngles.yaw + 180)
ang.roll = math.NormalizeAngle(ang.roll)
return ang
end
local function CalcView(ply, origin, angles, fov)
-- unrelated or unknown APC.
if not IsValid(ZAPC) or ViewMode == ZAPC_VIEW_UNRELATED then
return
end
-- Gunner.
if ViewMode == ZAPC_VIEW_GUNNER then
-- Get the attachment.
local att = ZAPC:GetAttachment(ZAPC:LookupAttachment('gun_def'))
-- In case att doesn't exist...
if not att then
return
end
local trace = util.TraceLine({ start = att.Pos, endpos = att.Pos + att.Ang:Up()*5, mask = MASK_SOLID_BRUSHONLY})
local view = {}
view.origin = trace.HitPos
if viewmode:GetInt() == 1 then
view.angles = ProcessAngle(LocalPlayer():EyeAngles())
else
view.angles = att.Ang
end
view.fov = Zoom
return view
elseif ViewMode == ZAPC_VIEW_DRIVER or ViewMode == ZAPC_VIEW_PASSENGER then
-- The next line is a lie.
angles = LocalPlayer():EyeAngles()
local view = {}
-- Third person
if viewmode:GetInt() == 1 or ViewMode == ZAPC_VIEW_PASSENGER then
-- This seems to be 25/35 all the time since a few updates (for whatever reason)
angles.pitch = 25
-- This should always be zero.
angles.roll = 0
-- If we fuck up, hardcore.
if tostring(angles.yaw):find('#', 1, true) then
angles.yaw = ZAPC:GetAngles().yaw+90
-- Try to "fix" it ourselves...
LocalPlayer():SetEyeAngles(angles)
end
local trace = util.TraceLine({ start = origin + ZAPC:GetUp()*5, endpos = origin - angles:Forward() * 350, mask = MASK_SOLID_BRUSHONLY })
view = {}
view.origin = trace.HitPos + trace.HitNormal * 5
view.angles = angles
view.fov = fov
-- First person
else
view = { origin = ZAPC:GetPos()+ZAPC:GetUp()*50-ZAPC:GetRight()*100, angles = (-ZAPC:GetRight()):Angle(), fov = 90 }
end
return view
end
end
hook.Add('CalcView', '_ZAPC.CalcView', CalcView)
-- UI
-- Returns a table to be used with draw.TexturedQuad when working with fixed stuff.
-- Let's call it SPRITE.
local function TexTable(fileName, _color, _x, _y, _w, _h)
local m, w, h = Material(fileName)
local tbl = { material = Material(fileName), color = _color, x = _x, y = _y, w = _w, h = _h }
return tbl
end
-- Draws the texture table.
local function DrawMaterial(tbl, overrideX, overrideY)
surface.SetMaterial(tbl.material)
surface.DrawTexturedRect(overrideX and overrideX or tbl.x, overrideY and overrideY or tbl.y, tbl.w, tbl.h)
end
-- Textures we are going to use.
local CHAIR_GUNNER_TABLE = TexTable('zapc_hud/seat_gunner.png', nil, 10, 20, 64, 64)
local CHAIR_DRIVER_TABLE = TexTable('zapc_hud/seat_driver.png', nil, 42, 80, 64, 64)
local CHAIR_PASSENGER_TABLE = TexTable('zapc_hud/seat.png', nil, 10, 140, 64, 64)
local SHELL_TABLE = TexTable('zapc_hud/rocket.png', nil, -30, ScrH() - 190, 128, 128)
local CROSSHAIR_TABLE = TexTable('zapc_hud/crosshair.png', nil, ScrW()/2 - 32, ScrH()/2 - 32, 128, 128)
-- Returns the name of somebody.
local function Nick(ply)
return IsValid(ply) and ((ply.GetCharacterName and ply:GetCharacterName()) or (ply.GetRPName and ply:GetRPName()) or ply:Nick()) or '(Empty)'
end
-- Draws.. a box.
local function DrawBox(x, y, w, h, color)
surface.SetDrawColor(color.r, color.g, color.b, color.a)
surface.DrawRect(x, y, w, h)
end
-- baboom.
local function LerpColor(fract, from, to)
return Color(to.r - (to.r-from.r)*fract, to.g - (to.g-from.g)*fract, to.b - (to.b-from.b)*fract, to.a-(to.a-from.a)*fract)
end
-- "Temporary" helper function.
local function MaxTextSize(currentWidth, currentHeight, text, x, y)
local w, h = surface.GetTextSize(text)
x, y = x or 0, y or 0
if currentWidth < w + x then
currentWidth = w + x
end
if currentHeight < h + y then
currentHeight = h + y
end
return currentWidth, currentHeight
end
-- woop woop woop woooop
local function HUDPaint()
if not IsValid(ZAPC) or ViewMode == ZAPC_VIEW_UNRELATED then
return
end
-- The prisoner one is easy!
if ViewMode == ZAPC_VIEW_PASSENGER then
if not HatchOpened then
draw.SimpleText('You are inside the APC. The hatch was locked.', 'ZAPC_HUD', ScrW()/2, ScrH()/2, Color(255, 255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
return
end
-- The color we draw stuff in. TODO: Change this to some fancy fading.
local healthFraction = math.Clamp(math.TimeFraction(0, ZAPC_MAX_HEALTH(), Health), 0, 1)
-- If we are destructing, we're using SINUS instead!
local Bullets, NextPrimary, NextSecondary = Bullets, NextPrimary, NextSecondary
local velocity = (7*lastVel + math.min(1, math.TimeFraction(0, 650, ZAPC:GetVelocity():Length()))) / 8 -- abuse of TimeFraction, sue me
lastVel = velocity
if Destructing then
healthFraction = math.sin(CurTime()*5)/2 + 0.5
Bullets = math.floor(math.sin(CurTime()*3)*25 + 25)
NextPrimary = CurTime() - ZAPC_PRIMARY_RELOAD_TIME()*(math.sin(CurTime()*6)/2+0.5)
NextSecondary = CurTime() - ZAPC_SECONDARY_RELOAD_TIME()*(math.sin(CurTime()*5)/2+0.5)
velocity = math.sin(CurTime()*10)/2 + 0.5
HatchOpened = math.sin(CurTime()*30)/4 > 0
end
local colorFrac = healthFraction - math.Clamp(1-math.TimeFraction(LastHealthLoss, LastHealthLoss + ZAPC_HUD_HEALTH_FLASH_TIME, CurTime()), 0, healthFraction)
local color = LerpColor(colorFrac, ZAPC_HUD_OKAY, ZAPC_HUD_NOT_OKAY)
local bgColor = LerpColor(colorFrac, ZAPC_HUD_OKAY_BG, ZAPC_HUD_NOT_OKAY_BG)
-- Some constants, move them out later
local gunnerTextX, gunnerTextY = 80, 40
local driverTextX, driverTextY = 112, 100
local hatchTextX, hatchTextY = 75, 170
-- Get the size of the box. As of now, no fancy resizing effect.
-- Gunner
surface.SetFont('ZAPC_HUD')
local boxX, boxY = 5, 15
boxW, boxH = 205 + boxX, 195 + boxY -- minimum box size
boxW, boxH = MaxTextSize(boxW, boxH, Nick(Gunner), gunnerTextX, gunnerTextY)
-- Driver
boxW, boxH = MaxTextSize(boxW, boxH, Nick(Driver), driverTextX, driverTextY)
-- Any passenger we might have
surface.SetFont('ZAPC_HUDSmall')
for k, v in pairs(Passengers) do
boxW, boxH = MaxTextSize(boxW, boxH, Nick(v), 75, 170 + k*22)
end
-- For the main interface.
draw.RoundedBoxEx(8, boxX, boxY, boxW - boxX + 5, boxH - boxY + 5, bgColor, true, true, true, true)
-- The bottom-left
draw.RoundedBoxEx(8, 5, ScrH() - 195, 105, 175, bgColor, true, true, true, false)
draw.RoundedBoxEx(8, 110, ScrH() - 70, 450, 50, bgColor, false, true, false, true)
-- And the right one
draw.RoundedBoxEx(8, ScrW() - 75, 8, 70, ScrH() - 16, bgColor, true, true, true, true)
-- Set the surface color.
surface.SetDrawColor(color)
-- Draw the seats.
DrawMaterial(CHAIR_PASSENGER_TABLE)
DrawMaterial(CHAIR_GUNNER_TABLE)
DrawMaterial(CHAIR_DRIVER_TABLE)
-- The names for those.
draw.SimpleText(Nick(Gunner), 'ZAPC_HUD', gunnerTextX, gunnerTextY, color, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
draw.SimpleText(Nick(Driver), 'ZAPC_HUD', driverTextX, driverTextY, color, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
for k, v in pairs(Passengers) do
draw.SimpleText(Nick(v), 'ZAPC_HUDSmall', 75, 170 + k*22, color, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
-- Draw the status
draw.SimpleText('Hatch ' .. (HatchOpened and 'opened' or 'locked'), 'ZAPC_HUD', hatchTextX, hatchTextY, HatchOpened and ZAPC_HUD_NOT_OKAY or color, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
-- Bullets.
local x, y = 20, ScrH() - 50
local bulletColor = Color(color.r, color.g, color.b, color.a)
local da = 0
if Bullets == 0 then
bulletColor = Color(color.r, color.g, color.b, 0)
elseif Bullets <= 20 then
bulletColor = LerpColor(math.Clamp(math.TimeFraction(10, 20, Bullets), 0, 1), color, ZAPC_HUD_NOT_OKAY)
da = TimedSin(ZAPC_HUD_BULLET_WARNING_FLASH_FREQ * (1.2-math.TimeFraction(1, 20, Bullets)), 0, 25, 0)*2 + -25 -- don't call your god damn parameters "min" and "max" if you aren't going to stick to it
end
local bulletTextColor = Color(bulletColor.r, bulletColor.g, bulletColor.b, bulletColor.a > 0 and (bulletColor.a + da) or 255) -- If we're reloading, just use full transparency.
for i = 1, 50 do
bulletColor.a = ((i <= Bullets and 255) or 120) + da
draw.RoundedBoxEx(4, x, y, 8, 12, bulletColor, true, true, false, false)
x = x + 10
end
draw.SimpleText(tostring(Bullets), 'ZAPC_HUD', 525, ScrH() - 30, bulletTextColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
-- The reloading bar.
local reloadProgress = math.min(math.TimeFraction(LastPrimary, NextPrimary, CurTime()), 1.0)
DrawBox(20, ScrH() - 35, Lerp(reloadProgress, 0, 500), 5, bulletTextColor)
reloadProgress = math.min(math.TimeFraction(LastSecondary, NextSecondary, CurTime()), 1.0)
local reloadColor = LerpColor(reloadProgress, color, ZAPC_HUD_NOT_OKAY)
surface.SetDrawColor(reloadColor)
-- Not using that weird, ugly thing that was in the old APC. Clipping for teh win.
render.SetScissorRect(SHELL_TABLE.x, SHELL_TABLE.y + SHELL_TABLE.h * (1-reloadProgress), SHELL_TABLE.x + SHELL_TABLE.w, SHELL_TABLE.y + SHELL_TABLE.h, true)
DrawMaterial(SHELL_TABLE)
render.SetScissorRect(0, 0, 0, 0, false)
draw.SimpleText(tostring(math.floor(reloadProgress * 100)) .. '%', 'ZAPC_HUD', 50, ScrH() - 73, reloadColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
-- Velocity
local vel = Lerp(velocity, 0, ScrH()-30)
DrawBox(ScrW() - 30, ScrH() - 15 - vel, 15, vel, color)
-- Health
local hbar = Lerp(healthFraction, 0, ScrH() - 30)
DrawBox(ScrW() - 65, ScrH() - 15 - hbar, 25, hbar, color)
-- Crossbar, if gunner
if ViewMode == ZAPC_VIEW_GUNNER then
local attach = ZAPC:GetAttachment(ZAPC:LookupAttachment('muzzle'))
local p = util.TraceLine({ start = attach.Pos - 15 * attach.Ang:Forward() + attach.Ang:Right(), endpos = attach.Pos - 15 * attach.Ang:Forward() + attach.Ang:Right() + attach.Ang:Forward()*16384, filter = { ZAPC }, mask = MASK_SHOT }).HitPos:ToScreen()
surface.SetDrawColor(color.r, color.g, color.b, color.a*0.8)
DrawMaterial(CROSSHAIR_TABLE, p.x - CROSSHAIR_TABLE.w/2, p.y - CROSSHAIR_TABLE.h/2)
end
end
hook.Add('HUDPaint', '_ZAPC.HUDPaint', HUDPaint)
local function PreDrawHUD()
if ViewMode == ZAPC_VIEW_PASSENGER and not HatchOpened then
local color = ZAPC:GetColor()
surface.SetDrawColor(color.r/5, color.g/5, color.b/5, 230)
-- noo idea!
surface.DrawRect(-ScrW(), -ScrH(), ScrW()*2, ScrH()*2)
end
end
hook.Add('PreDrawHUD', '_ZAPC.PreDrawHUD', PreDrawHUD)
-- I feel bad for abusing this like that.
local ToytownMaterial = Material('pp/toytown-top')
ToytownMaterial:SetTexture('$fbtexture', render.GetScreenEffectTexture())
local function RenderScreenspaceEffects()
if ViewMode == ZAPC_VIEW_PASSENGER and not HatchOpened then
cam.Start2D()
surface.SetMaterial(ToytownMaterial)
surface.SetDrawColor(255, 255, 255, 255)
for i = 1, 25 do
render.UpdateScreenEffectTexture()
surface.DrawTexturedRect(0, 0, ScrW(), ScrH()*2.5)
end
cam.End2D()
end
end
hook.Add('RenderScreenspaceEffects', '_ZAPC.RenderScreenspaceEffects', RenderScreenspaceEffects)
local function Tick()
if ViewMode == ZAPC_VIEW_GUNNER then
Zoom = math.Clamp(Zoom + (LocalPlayer():KeyDown(IN_ZOOM) and -ZAPC_ZOOM_RATE or ZAPC_ZOOM_RATE), ZAPC_MAX_ZOOM, ZAPC_MIN_ZOOM)
end
end
hook.Add('Tick', '_ZAPC.Tick', Tick)
local function PlayerBindPress(ply, bind, pressed)
if ViewMode ~= ZAPC_VIEW_UNRELATED and bind:find('+duck') and pressed then
RunConsoleCommand('zapc_viewmode', math.abs(viewmode:GetInt()-1))
return true
end
end
hook.Add('PlayerBindPress', '_ZAPC.PlayerBindPress', PlayerBindPress)
local function HUDShouldDraw(name)
if ViewMode ~= ZAPC_VIEW_UNRELATED and (name == 'CHudHealth' or name == 'CHudBattery') then
return false
end
end
hook.Add('HUDShouldDraw', '_ZAPC.HUDShouldDraw', HUDShouldDraw)
-- Notifications for "new" players.
local function Notify(str, length, delay)
timer.Simple(delay or 0, function()
notification.AddLegacy(str, NOTIFY_HINT, length)
end)
end
local notifyDriver, notifyGunner = true, true
local function DoDriverNotifications()
notifyDriver = false
Notify('Driver: To start the engine and release the breaks, press the left mouse button (+attack)', 10)
--~ Notify('Driver: To switch to the lower/higher gear, press the right mouse button (+attack2)', 8, 8)
Notify('Driver: To switch seats, press shift (+speed)', 10, 14)
Notify('Driver: To open or close the hatch, press Alt (+walk)', 10, 22)
Notify('Driver: To toggle the siren, press R (+reload)', 10, 30)
end
local function DoGunnerNotifications()
notifyGunner = false
Notify('Gunner: To fire the turret, press your left mouse button (+attack)', 10)
Notify('Gunner: To fire rockets, press your right mouse button (+attack2)', 10)
Notify('Gunner: To reload, press R (+reload)', 8, 8)
end
--[[ User Messages ]]--
-- Updates ViewMode, if necessary.
usermessage.Hook('ZAPC_VU', function(msg)
ViewMode = tonumber(msg:ReadChar())
-- Don't deal with notification stuff if we're disabled.
if GetConVarNumber( "cl_showhints" ) == 0 then
return
end
if notifyDriver and ViewMode == ZAPC_VIEW_DRIVER then
DoDriverNotifications()
elseif notifyGunner and ViewMode == ZAPC_VIEW_GUNNER then
DoGunnerNotifications()
end
end)
-- The APC we (were) last in.
usermessage.Hook('ZAPC_AU', function(msg) ZAPC = msg:ReadEntity() LocalPlayer():SetEyeAngles(Angle(25, ZAPC:GetAngles().yaw + 90, 0)) LastHealthLoss = 0 Destructing = false lastVel = 0 end) -- reset the health loss because the interface weirds around otherwise.
-- Bullet.
usermessage.Hook('ZAPC_BU', function(msg) Bullets = tonumber(msg:ReadChar()) end)
-- Actions.
usermessage.Hook('ZAPC_LPU', function() LastPrimary, NextPrimary = CurTime(), CurTime() + ZAPC_PRIMARY_RELOAD_TIME() end)
usermessage.Hook('ZAPC_LSU', function() LastSecondary, NextSecondary = CurTime(), CurTime() + ZAPC_SECONDARY_RELOAD_TIME() end)
-- Guys inside the APC.
usermessage.Hook('ZAPC_PU', function(msg) Driver = msg:ReadEntity() Gunner = msg:ReadEntity() Passengers = {} for i = 1, ZAPC_MAX_PASSENGERS() do local ent = msg:ReadEntity() if IsValid(ent) then table.insert(Passengers, ent) end end end)
-- Health
usermessage.Hook('ZAPC_HU', function(msg) Health = msg:ReadShort() LastHealthLoss = CurTime() end)
-- Destructoid
usermessage.Hook('ZAPC_SD', function(msg) Destructing = true end)
-- Hatch opening update.
usermessage.Hook('ZAPC_HOU', function(msg) HatchOpened = msg:ReadBool() end)
--[[ Debugging the APC and other addons. ]]--
local function WrapRelated(event, key)
local func = hook.GetTable()[event][key]
if not func then
return
end
hook.Add(event, key, function(...) if ViewMode == ZAPC_VIEW_UNRELATED then return func(...) end end)
end
local function Initialize()
--[[
The official
HALL OF STOOPID
]]--
-- 107985981, disrespecting about everyone as soon as a NWVar was set
WrapRelated('CalcView', "RenderWithDifferentNearPlane")
end
hook.Add('Initialize', '_ZAPC.Initialize', Initialize)
-- In case this happens again, here's a few debuggers.
local function DumpHooks(ply, cmd, args)
if IsValid(ply) and not game.SinglePlayer() then
return
end
local hooks = { 'CalcView', 'HUDPaint', 'PreDrawHUD', 'PlayerBindPress' }
print("\n[ZAPC] Dumping possible client hook collisions...")
for _, h in pairs(hooks) do
print("[ZAPC] " .. h)
for k, v in pairs(hook.GetTable()[h]) do
print('[ZAPC] "' .. tostring(k) .. '"')
end
print()
end
print("[ZAPC] Hooks dumped.")
end
concommand.Add('zapc_dumphooks_cl', DumpHooks, nil, "Dumps all hooks that could possibly cause havok on the client.")