Files
wnsrc/gamemodes/terrortown/entities/weapons/weapon_ttt_wtester.lua

808 lines
22 KiB
Lua
Raw Normal View History

2024-08-04 23:12:27 +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/
--]]
-- DNA Scanner
AddCSLuaFile()
DEFINE_BASECLASS "weapon_tttbase"
SWEP.HoldType = "normal"
if CLIENT then
SWEP.PrintName = "dna_name"
SWEP.Slot = 8
SWEP.ViewModelFOV = 10
SWEP.DrawCrosshair = false
SWEP.EquipMenuData = {
type = "item_weapon",
desc = "dna_desc"
};
SWEP.Icon = "vgui/ttt/icon_wtester"
end
SWEP.Base = "weapon_tttbase"
SWEP.ViewModel = "models/weapons/v_crowbar.mdl"
SWEP.WorldModel = "models/props_lab/huladoll.mdl"
SWEP.Primary.ClipSize = -1
SWEP.Primary.DefaultClip = -1
SWEP.Primary.Automatic = false
SWEP.Primary.Delay = 1
SWEP.Primary.Ammo = "none"
SWEP.Secondary.ClipSize = -1
SWEP.Secondary.DefaultClip = -1
SWEP.Secondary.Automatic = false
SWEP.Secondary.Ammo = "none"
SWEP.Secondary.Delay = 2
SWEP.Kind = WEAPON_ROLE
SWEP.CanBuy = nil -- no longer a buyable thing
SWEP.WeaponID = AMMO_WTESTER
SWEP.InLoadoutFor = {ROLE_DETECTIVE}
--SWEP.AllowDrop = false
SWEP.AutoSpawnable = false
SWEP.NoSights = true
SWEP.Range = 175
SWEP.ItemSamples = {}
SWEP.NowRepeating = nil
local MAX_ITEM = 30
SWEP.MaxItemSamples = MAX_ITEM
local CHARGE_DELAY = 0.1
local CHARGE_RATE = 3
local MAX_CHARGE = 1250
local SAMPLE_PLAYER = 1
local SAMPLE_ITEM = 2
AccessorFuncDT(SWEP, "charge", "Charge")
AccessorFuncDT(SWEP, "last_scanned", "LastScanned")
if CLIENT then
CreateClientConVar("ttt_dna_scan_repeat", 1, true, true)
else
function SWEP:GetRepeating()
local ply = self:GetOwner()
return IsValid(ply) and ply:GetInfoNum("ttt_dna_scan_repeat", 1) == 1
end
end
SWEP.NextCharge = 0
function SWEP:SetupDataTables()
self:DTVar("Int", 0, "charge")
self:DTVar("Int", 1, "last_scanned")
return self.BaseClass.SetupDataTables(self)
end
function SWEP:Initialize()
self:SetCharge(MAX_CHARGE)
self:SetLastScanned(-1)
if CLIENT then
self:AddHUDHelp("dna_help_primary", "dna_help_secondary", true)
end
return self.BaseClass.Initialize(self)
end
local beep_miss = Sound("player/suit_denydevice.wav")
function SWEP:PrimaryAttack()
self:SetNextPrimaryFire( CurTime() + self.Primary.Delay )
-- will be tracing against players
self:GetOwner():LagCompensation(true)
local spos = self:GetOwner():GetShootPos()
local sdest = spos + (self:GetOwner():GetAimVector() * self.Range)
local tr = util.TraceLine({start=spos, endpos=sdest, filter=self:GetOwner(), mask=MASK_SHOT})
local ent = tr.Entity
if IsValid(ent) and (not ent:IsPlayer()) then
if SERVER then
if ent:GetClass() == "prop_ragdoll" and ent.killer_sample then
if CORPSE.GetFound(ent, false) then
self:GatherRagdollSample(ent)
else
self:Report("dna_identify")
end
elseif ent.fingerprints and #ent.fingerprints > 0 then
self:GatherObjectSample(ent)
else
self:Report("dna_notfound")
end
end
else
if CLIENT then
self:GetOwner():EmitSound(beep_miss)
end
end
self:GetOwner():LagCompensation(false)
end
function SWEP:GatherRagdollSample(ent)
local sample = ent.killer_sample or {t=0, killer=nil}
local ply = sample.killer
if (not IsValid(ply)) and sample.killer_sid64 then
ply = player.GetBySteamID64(sample.killer_sid64)
end
if IsValid(ply) then
if sample.t < CurTime() then
self:Report("dna_decayed")
return
end
local added = self:AddPlayerSample(ent, ply)
if not added then
self:Report("dna_limit")
else
self:Report("dna_killer")
if self:GetRepeating() and self:GetCharge() == MAX_CHARGE then
self:PerformScan(#self.ItemSamples)
end
end
elseif ply != nil then
-- not valid but not nil -> disconnected?
self:Report("dna_no_killer")
else
self:Report("dna_notfound")
end
end
function SWEP:GatherObjectSample(ent)
if ent:GetClass() == "ttt_c4" and ent:GetArmed() then
self:Report("dna_armed")
else
local collected, old, own = self:AddItemSample(ent)
if collected == -1 then
self:Report("dna_limit")
else
self:Report("dna_object", {num = collected})
end
end
end
function SWEP:Report(msg, params)
LANG.Msg(self:GetOwner(), msg, params)
end
function SWEP:AddPlayerSample(corpse, killer)
if #self.ItemSamples < self.MaxItemSamples then
local prnt = {source=corpse, ply=killer, type=SAMPLE_PLAYER, cls=killer:GetClass()}
if not table.HasTable(self.ItemSamples, prnt) then
table.insert(self.ItemSamples, prnt)
DamageLog("SAMPLE:\t " .. self:GetOwner():Nick() .. " retrieved DNA of " .. (IsValid(killer) and killer:Nick() or "<disconnected>") .. " from corpse of " .. (IsValid(corpse) and CORPSE.GetPlayerNick(corpse) or "<invalid>"))
hook.Call("TTTFoundDNA", GAMEMODE, self:GetOwner(), killer, corpse)
end
return true
end
return false
end
function SWEP:AddItemSample(ent)
if #self.ItemSamples < self.MaxItemSamples then
table.Shuffle(ent.fingerprints)
local new = 0
local old = 0
local own = 0
for _, p in pairs(ent.fingerprints) do
local prnt = {source=ent, ply=p, type=SAMPLE_ITEM, cls=ent:GetClass()}
if p == self:GetOwner() then
own = own + 1
elseif table.HasTable(self.ItemSamples, prnt) then
old = old + 1
else
table.insert(self.ItemSamples, prnt)
DamageLog("SAMPLE:\t " .. self:GetOwner():Nick() .. " retrieved DNA of " .. (IsValid(p) and p:Nick() or "<disconnected>") .. " from " .. ent:GetClass())
new = new + 1
hook.Call("TTTFoundDNA", GAMEMODE, self:GetOwner(), p, ent)
end
end
return new, old, own
end
return -1
end
function SWEP:RemoveItemSample(idx)
if self.ItemSamples[idx] then
if self:GetLastScanned() == idx then
self:ClearScanState()
end
table.remove(self.ItemSamples, idx)
self:SendPrints(false)
end
end
function SWEP:SecondaryAttack()
self:SetNextSecondaryFire( CurTime() + 0.05 )
if CLIENT then return end
self:SendPrints(true)
end
if SERVER then
-- Sending this all in one umsg limits the max number of samples. 17 player
-- samples and 20 item samples (with 20 matches) has been verified as
-- working in the old DNA sampler.
function SWEP:SendPrints(should_open)
net.Start("TTT_ShowPrints", self:GetOwner())
net.WriteBit(should_open)
net.WriteUInt(#self.ItemSamples, 8)
for k, v in ipairs(self.ItemSamples) do
net.WriteString(v.cls)
end
net.Send(self:GetOwner())
end
function SWEP:SendScan(pos)
local clear = (pos == nil) or (not IsValid(self:GetOwner()))
net.Start("TTT_ScanResult", self:GetOwner())
net.WriteBit(clear)
if not clear then
net.WriteVector(pos)
end
net.Send(self:GetOwner())
end
function SWEP:ClearScanState()
self:SetLastScanned(-1)
self.NowRepeating = nil
self:SendScan(nil)
end
local function GetScanTarget(sample)
if not sample then return end
local target = sample.ply
if not IsValid(target) then return end
-- decoys always take priority, even after death
if IsValid(target.decoy) then
target = target.decoy
elseif not target:IsTerror() then
-- fall back to ragdoll, as long as it's not destroyed
target = target.server_ragdoll
if not IsValid(target) then return end
end
return target
end
function SWEP:PerformScan(idx, repeated)
if self:GetCharge() < MAX_CHARGE then return end
local sample = self.ItemSamples[idx]
if (not sample) or (not IsValid(self:GetOwner())) then
if repeated then self:ClearScanState() end
return
end
local target = GetScanTarget(sample)
if not IsValid(target) then
self:Report("dna_gone")
self:SetCharge(self:GetCharge() - 50)
if repeated then self:ClearScanState() end
return
end
local pos = target:LocalToWorld(target:OBBCenter())
self:SendScan(pos)
self:SetLastScanned(idx)
self.NowRepeating = self:GetRepeating()
local dist = math.ceil(self:GetOwner():GetPos():Distance(pos))
self:SetCharge(math.max(0, self:GetCharge() - math.max(50, dist / 2)))
end
function SWEP:Think()
if self:GetCharge() < MAX_CHARGE then
if self.NextCharge < CurTime() then
self:SetCharge(math.min(MAX_CHARGE, self:GetCharge() + CHARGE_RATE))
self.NextCharge = CurTime() + CHARGE_DELAY
end
elseif self.NowRepeating and IsValid(self:GetOwner()) then
-- owner changed his mind since running last scan?
if self:GetRepeating() then
self:PerformScan(self:GetLastScanned(), true)
else
self.NowRepeating = self:GetRepeating()
end
end
return true
end
end
-- Helper to get at a player's scanner, if he has one
local function GetTester(ply)
if IsValid(ply) then
local tester = ply:GetActiveWeapon()
if IsValid(tester) and tester:GetClass() == "weapon_ttt_wtester" then
return tester
end
end
return nil
end
if CLIENT then
local T = LANG.GetTranslation
local PT = LANG.GetParamTranslation
local TT = LANG.TryTranslation
function SWEP:DrawHUD()
self:DrawHelp()
local spos = self:GetOwner():GetShootPos()
local sdest = spos + (self:GetOwner():GetAimVector() * self.Range)
local tr = util.TraceLine({start=spos, endpos=sdest, filter=self:GetOwner(), mask=MASK_SHOT})
local length = 20
local gap = 6
local can_sample = false
local ent = tr.Entity
if IsValid(ent) then
-- weapon or dropped equipment
if ((ent:IsWeapon() or ent.CanHavePrints) or
-- knife in corpse, or a ragdoll
ent:GetNWBool("HasPrints", false) or
(ent:GetClass() == "prop_ragdoll" and
CORPSE.GetPlayerNick(ent, false) and
CORPSE.GetFound(ent, false))) then
surface.SetDrawColor(0, 255, 0, 255)
gap = 0
can_sample = true
else
surface.SetDrawColor(255, 0, 0, 200)
gap = 0
end
else
surface.SetDrawColor(255, 255, 255, 200)
end
local x = ScrW() / 2.0
local y = ScrH() / 2.0
surface.DrawLine( x - length, y, x - gap, y )
surface.DrawLine( x + length, y, x + gap, y )
surface.DrawLine( x, y - length, x, y - gap )
surface.DrawLine( x, y + length, x, y + gap )
if ent and can_sample then
surface.SetFont("DefaultFixedDropShadow")
surface.SetTextColor(0, 255, 0, 255)
surface.SetTextPos( x + length*2, y - length*2 )
surface.DrawText(T("dna_hud_type") .. ": " .. (ent:GetClass() == "prop_ragdoll" and T("dna_hud_body") or T("dna_hud_item")))
surface.SetTextPos( x + length*2, y - length*2 + 15)
surface.DrawText("ID: #" .. ent:EntIndex())
end
end
local basedir = "vgui/ttt/icon_"
local function GetDisplayData(cls)
local wep = util.WeaponForClass(cls)
local img = basedir .. "nades"
local name = "something"
if cls == "player" then
img = basedir .. "corpse"
name = "corpse"
elseif wep then
img = wep.Icon or img
name = wep.PrintName or name
end
return img, name
end
local last_panel_selected = 1
local function ShowPrintsPopup(item_prints, tester)
local m = 10
local bw, bh = 100, 25
local dpanel = vgui.Create("DFrame")
local w, h = 400, 250
dpanel:SetSize(w, h)
dpanel:AlignRight(5)
dpanel:AlignBottom(5)
dpanel:SetTitle(T("dna_menu_title"))
dpanel:SetVisible(true)
dpanel:ShowCloseButton(true)
dpanel:SetMouseInputEnabled(true)
local wrap = vgui.Create("DPanel", dpanel)
wrap:StretchToParent(m/2, m + 15, m/2, m + bh)
wrap:SetPaintBackground(false)
-- item sample listing
local ilist = vgui.Create("DPanelSelect", wrap)
ilist:StretchToParent(0,0,0,0)
ilist:EnableHorizontal(true)
ilist:SetSpacing(1)
ilist:SetPadding(1)
ilist.OnActivePanelChanged = function(s, old, new)
last_panel_selected = new and new.key or 1
end
ilist.OnScan = function(s, scanned_pnl)
for k, pnl in pairs(s:GetItems()) do
pnl:SetIconColor(COLOR_LGRAY)
end
scanned_pnl:SetIconColor(COLOR_WHITE)
end
if ilist.VBar then
ilist.VBar:Remove()
ilist.VBar = nil
end
local iscroll = vgui.Create("DHorizontalScroller", ilist)
iscroll:SetPos(3,1)
iscroll:SetSize(363, 66)
iscroll:SetOverlap(1)
iscroll.LoadFrom = function(s, tbl, layout)
ilist:Clear(true)
ilist.SelectedPanel = nil
-- Scroller has no Clear()
for k, pnl in pairs(s.Panels) do
if IsValid(pnl) then
pnl:Remove()
end
end
s.Panels = {}
local last_scan = tester and tester:GetLastScanned() or -1
for k, v in ipairs(tbl) do
local ic = vgui.Create("SimpleIcon", ilist)
ic:SetIconSize(64)
local img, name = GetDisplayData(v)
ic:SetIcon(img)
local tip = PT("dna_menu_sample", {source = TT(name) or "???"})
ic:SetTooltip(tip)
ic.key = k
ic.val = v
if layout then
ic:PerformLayout()
end
ilist:AddPanel(ic)
s:AddPanel(ic)
if k == last_panel_selected then
ilist:SelectPanel(ic)
end
if last_scan > 0 then
ic:SetIconColor(last_scan == k and COLOR_WHITE or COLOR_LGRAY)
end
end
iscroll:InvalidateLayout()
end
iscroll:LoadFrom(item_prints)
local delwrap = vgui.Create("DPanel", wrap)
delwrap:SetPos(m, 70)
delwrap:SetSize(370, bh)
delwrap:SetPaintBackground(false)
local delitem = vgui.Create("DButton", delwrap)
delitem:SetPos(0,0)
delitem:SetSize(bw, bh)
delitem:SetText(T("dna_menu_remove"))
delitem.DoClick = function()
if IsValid(ilist) and IsValid(ilist.SelectedPanel) then
local idx = ilist.SelectedPanel.key
RunConsoleCommand("ttt_wtester_remove", idx)
end
end
delitem.Think = function(s)
if IsValid(ilist) and IsValid(ilist.SelectedPanel) then
s:SetEnabled(true)
else
s:SetEnabled(false)
end
end
local delhlp = vgui.Create("DLabel", delwrap)
delhlp:SetPos(bw + m, 0)
delhlp:SetText(T("dna_menu_help1"))
delhlp:SizeToContents()
-- hammer out layouts
wrap:PerformLayout()
-- scroller needs to sort itself out so it displays all icons it should
iscroll:PerformLayout()
local mwrap = vgui.Create("DPanel", wrap)
mwrap:SetPaintBackground(false)
mwrap:SetPos(m,100)
mwrap:SetSize(370, 90)
local bar = vgui.Create("TTTProgressBar", mwrap)
bar:SetSize(370, 35)
bar:SetPos(0, 0)
bar:CenterHorizontal()
bar:SetMin(0)
bar:SetMax(MAX_CHARGE)
bar:SetValue(tester and math.min(MAX_CHARGE, tester:GetCharge()))
bar:SetColor(COLOR_GREEN)
bar:LabelAsPercentage()
local state = vgui.Create("DLabel", bar)
state:SetSize(0, 35)
state:SetPos(10, 6)
state:SetFont("Trebuchet22")
state:SetText(T("dna_menu_ready"))
state:SetTextColor(COLOR_WHITE)
state:SizeToContents()
local scan = vgui.Create("DButton", mwrap)
scan:SetText(T("dna_menu_scan"))
scan:SetSize(bw, bh)
scan:SetPos(0, 40)
scan:SetEnabled(false)
scan.DoClick = function(s)
if IsValid(ilist) then
local i = ilist.SelectedPanel
if IsValid(i) then
RunConsoleCommand("ttt_wtester_scan", i.key)
ilist:OnScan(i)
end
end
end
local dcheck = vgui.Create("DCheckBoxLabel", mwrap)
dcheck:SetPos(0, 70)
dcheck:SetText(T("dna_menu_repeat"))
dcheck:SetIndent(7)
dcheck:SizeToContents()
dcheck:SetConVar("ttt_dna_scan_repeat")
--dcheck:SetValue(tester and tester:GetRepeating())
local scanhlp = vgui.Create("DLabel", mwrap)
scanhlp:SetPos(bw + m, 40)
scanhlp:SetText(T("dna_menu_help2"))
scanhlp:SizeToContents()
-- CLOSE
local dbut = vgui.Create("DButton", dpanel)
dbut:SetSize(bw, bh)
dbut:SetPos(m, h - bh - m/1.5)
dbut:CenterHorizontal()
dbut:SetText(T("close"))
dbut.DoClick = function() dpanel:Close() end
dpanel:MakePopup()
dpanel:SetKeyboardInputEnabled(false)
-- Expose updating fns
dpanel.UpdatePrints = function(s, its)
if IsValid(iscroll) then
iscroll:LoadFrom(its)
end
end
dpanel.Think = function(s)
if IsValid(bar) and IsValid(scan) and tester then
local charge = tester:GetCharge()
bar:SetValue(math.min(MAX_CHARGE, charge))
if charge < MAX_CHARGE then
bar:SetColor(COLOR_RED)
state:SetText(T("dna_menu_charge"))
state:SizeToContents()
scan:SetEnabled(false)
else
bar:SetColor(COLOR_GREEN)
if IsValid(ilist) and IsValid(ilist.SelectedPanel) then
scan:SetEnabled(true)
state:SetText(T("dna_menu_ready"))
state:SizeToContents()
else
state:SetText(T("dna_menu_select"))
state:SizeToContents()
scan:SetEnabled(false)
end
end
end
end
return dpanel
end
local printspanel = nil
local function RecvPrints()
local should_open = net.ReadBit() == 1
local num = net.ReadUInt(8)
local item_prints = {}
for i=1, num do
local ent = net.ReadString()
table.insert(item_prints, ent)
end
if should_open then
if IsValid(printspanel) then
printspanel:Remove()
end
local tester = GetTester(LocalPlayer())
printspanel = ShowPrintsPopup(item_prints, tester)
else
if IsValid(printspanel) then
printspanel:UpdatePrints(item_prints)
end
end
end
net.Receive("TTT_ShowPrints", RecvPrints)
local beep_success = Sound("buttons/blip2.wav")
local function RecvScan()
local clear = net.ReadBit() == 1
if clear then
RADAR.samples = {}
RADAR.samples_count = 0
return
end
local target_pos = net.ReadVector()
if not target_pos then return end
RADAR.samples = {
{pos = target_pos}
};
RADAR.samples_count = 1
surface.PlaySound(beep_success)
end
net.Receive("TTT_ScanResult", RecvScan)
function SWEP:ClosePrintsPanel()
if IsValid(printspanel) then
printspanel:Close()
end
end
else -- SERVER
local function ScanPrint(ply, cmd, args)
if #args != 1 then return end
local tester = GetTester(ply)
if IsValid(tester) then
local i = tonumber(args[1])
if i then
tester:PerformScan(i)
end
end
end
concommand.Add("ttt_wtester_scan", ScanPrint)
local function RemoveSample(ply, cmd, args)
if #args != 1 then return end
local idx = tonumber(args[1])
if not idx then return end
local tester = GetTester(ply)
if IsValid(tester) then
tester:RemoveItemSample(idx)
end
end
concommand.Add("ttt_wtester_remove", RemoveSample)
end
function SWEP:OnRemove()
if CLIENT then
self:ClosePrintsPanel()
end
end
function SWEP:OnDrop()
end
function SWEP:PreDrop()
if IsValid(self:GetOwner()) then
self:GetOwner().scanner_weapon = nil
end
end
function SWEP:Reload()
return false
end
function SWEP:Deploy()
if SERVER and IsValid(self:GetOwner()) then
self:GetOwner():DrawViewModel(false)
self:GetOwner().scanner_weapon = self
end
return true
end
if CLIENT then
function SWEP:DrawWorldModel()
if not IsValid(self:GetOwner()) then
self:DrawModel()
end
end
end