TTT DNA Scanner error?

Okay so, I bought a script off of coderhire a while back which modified the DNA scanner. It overwrote my old DNA scanner file.
So, I went in the TTT files under my steam name and put that in the server. But it doesn’t work and gives me this error.

gamemodes/terrortown/entities/weapons/weapon_ttt_wtester/shared.lua:266: Calling net.Start with unpooled message name [http://goo.gl/qcx0y]

It starts from line 261 and finishes at 275.


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.Owner)
        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.Owner)
   end

Or, if you wouldn’t mind posting your server DNA scanner file that would be great. I have no knowledge of Lua and help would be greatly appreciated. Thanks!

https://github.com/garrynewman/garrysmod/blob/master/garrysmod/gamemodes/terrortown/entities/weapons/weapon_ttt_wtester/shared.lua

That doesn’t work. Could there be any other lua files associated with this one? I’m not sure wether I replaced another one :confused:

Is your TTT Base up to date? https://github.com/garrynewman/garrysmod/blob/master/garrysmod/gamemodes/terrortown/entities/weapons/weapon_tttbase/shared.lua

Add the following line of code at the top of the script:


util.AddNetworkString("TTT_ShowPrints")

It still doesn’t work :s The tttwtester script is the same as the one here: https://github.com/garrynewman/garrysmod/blob/master/garrysmod/gamemodes/terrortown/entities/weapons/weapon_tttbase/shared.lua

This is the full error.

[ERROR] gamemodes/terrortown/entities/weapons/weapon_ttt_wtester/shared.lua:279: Calling net.Start with unpooled message name [http://goo.gl/qcx0y]

  1. Start - [C]:-1
  2. SendScan - gamemodes/terrortown/entities/weapons/weapon_ttt_wtester/shared.lua:279
  3. PerformScan - gamemodes/terrortown/entities/weapons/weapon_ttt_wtester/shared.lua:331
  4. GatherRagdollSample - gamemodes/terrortown/entities/weapons/weapon_ttt_wtester/shared.lua:161
  5. oldprimary - gamemodes/terrortown/entities/weapons/weapon_ttt_wtester/shared.lua:120
  6. unknown - addons/damagelog-master/lua/sv_damageinfos.lua:34
    Basically I can’t open the mouse 2 menu with the DNA Scanner nor does it work when I gather DNA on someone. I’m sorry if this is too much you don’t need to help. It would be appreciated though.

So this is the damageinfos.lua file here.



util.AddNetworkString("DL_AskDamageInfos")
util.AddNetworkString("DL_SendDamageInfos")
util.AddNetworkString("DL_AskShootLogs")
util.AddNetworkString("DL_SendShootLogs")

function Damagelog:shootCallback(weapon)
	local owner = weapon.Owner
	if GetRoundState() == ROUND_ACTIVE then
		if self.ShootTables[self.CurrentRound][self.Time] then
			local info = { owner:Nick(), weapon:GetClass() }
			table.insert(self.ShootTables[self.CurrentRound][self.Time], info)			
		else
			table.insert(self.ShootTables[self.CurrentRound], self.Time, {})
			local info = { owner:Nick(), weapon:GetClass() }
			table.insert(self.ShootTables[self.CurrentRound][self.Time], info)
		end
	end
end
	 
function Damagelog:DamagelogInfos()
	for k,v in pairs(weapons.GetList()) do		
		if v.Base == "weapon_tttbase" then
			if not v.PrimaryAttack then
				v.PrimaryAttack = function(wep)
					wep.BaseClass.PrimaryAttack(wep)
					if wep.BaseClass.CanPrimaryAttack(wep) and IsValid(wep.Owner) then
						self:shootCallback(wep)
					end
				end
			else
				local oldprimary = v.PrimaryAttack
				v.PrimaryAttack = function(wep)
					oldprimary(wep)
					Damagelog:shootCallback(wep)
				end
			end
		end
	end
end
	
hook.Add("Initialize", "Initialize_DamagelogInfos", function()	
	Damagelog:DamagelogInfos()
end)

function Damagelog:SendDamageInfos(ply, t, att, victim, round)
	local results = {}
	local found = false
	for k,v in pairs(self.ShootTables[round] or {}) do
	    if k >= t - 10 and k <= t then
		    for s,i in pairs(v) do
		        if i[1] == victim or i[1] == att then
		            if results[k] == nil then
					    table.insert(results, k, {})
					end
					table.insert(results[k], i)
			        found = true
				end
			end
		end
	end
	local beg = t - 10
	if found then
		net.Start("DL_SendDamageInfos")
		net.WriteUInt(0,1)
		net.WriteUInt(beg, 32)
		net.WriteUInt(t, 32)
		net.WriteTable(results)
		net.WriteString(victim)
		net.WriteString(att)
		net.Send(ply)
	else 
		net.Start("DL_SendDamageInfos")
		net.WriteUInt(1,1)
		net.WriteUInt(beg, 32)
		net.WriteUInt(t, 32)
		net.WriteString(victim)
		net.WriteString(att)
		net.Send(ply)
    end
end 

net.Receive("DL_AskDamageInfos", function(_, ply)
	local time = net.ReadUInt(32)
	local attacker = net.ReadString()
	local victim = net.ReadString()
	local round = net.ReadUInt(32)
	Damagelog:SendDamageInfos(ply, time, attacker, victim, round)
end)

local orderedPairs = Damagelog.orderedPairs
net.Receive("DL_AskShootLogs", function(_, ply)
	if not ply:CanUseDamagelog() then return end
	local data = Damagelog.ShootTables[net.ReadUInt(8)]
	if not data then return end
	data = table.Copy(data)
	local count = table.Count(data)
	local i = 0
	if count <= 0 then
		net.Start("DL_SendShootLogs")
		net.WriteTable({"empty"})
		net.WriteUInt(1, 1)
		net.Send(ply)
	else
		for k,v in orderedPairs(data) do
			i = i + 1
			net.Start("DL_SendShootLogs")
			net.WriteTable(v)
			net.WriteUInt(i == count and 1 or 0, 1)
			net.Send(ply)
		end
	end
end)

From line 1 to 113.

Is there something wrong with either one?

bump

Could you please post the contents of your garrysmod/gamemodes/terrortown/gamemode/init.lua file?


---- Trouble in Terrorist Town

AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
AddCSLuaFile("cl_hud.lua")
AddCSLuaFile("cl_msgstack.lua")
AddCSLuaFile("cl_hudpickup.lua")
AddCSLuaFile("cl_keys.lua")
AddCSLuaFile("cl_wepswitch.lua")
AddCSLuaFile("cl_awards.lua")
AddCSLuaFile("cl_scoring_events.lua")
AddCSLuaFile("cl_scoring.lua")
AddCSLuaFile("cl_popups.lua")
AddCSLuaFile("cl_equip.lua")
AddCSLuaFile("equip_items_shd.lua")
AddCSLuaFile("cl_help.lua")
AddCSLuaFile("cl_scoreboard.lua")
AddCSLuaFile("cl_tips.lua")
AddCSLuaFile("cl_voice.lua")
AddCSLuaFile("scoring_shd.lua")
AddCSLuaFile("util.lua")
AddCSLuaFile("lang_shd.lua")
AddCSLuaFile("corpse_shd.lua")
AddCSLuaFile("player_ext_shd.lua")
AddCSLuaFile("weaponry_shd.lua")
AddCSLuaFile("cl_radio.lua")
AddCSLuaFile("cl_radar.lua")
AddCSLuaFile("cl_tbuttons.lua")
AddCSLuaFile("cl_disguise.lua")
AddCSLuaFile("cl_transfer.lua")
AddCSLuaFile("cl_search.lua")
AddCSLuaFile("cl_targetid.lua")
AddCSLuaFile("vgui/ColoredBox.lua")
AddCSLuaFile("vgui/SimpleIcon.lua")
AddCSLuaFile("vgui/ProgressBar.lua")
AddCSLuaFile("vgui/ScrollLabel.lua")
AddCSLuaFile("vgui/sb_main.lua")
AddCSLuaFile("vgui/sb_row.lua")
AddCSLuaFile("vgui/sb_team.lua")
AddCSLuaFile("vgui/sb_info.lua")

include("shared.lua")

include("karma.lua")
include("entity.lua")
include("scoring_shd.lua")
include("radar.lua")
include("admin.lua")
include("traitor_state.lua")
include("propspec.lua")
include("weaponry.lua")
include("gamemsg.lua")
include("ent_replace.lua")
include("scoring.lua")
include("corpse.lua")
include("player_ext_shd.lua")
include("player_ext.lua")
include("player.lua")
include("tags.lua")

CreateConVar("ttt_roundtime_minutes", "10", FCVAR_NOTIFY)
CreateConVar("ttt_preptime_seconds", "30", FCVAR_NOTIFY)
CreateConVar("ttt_posttime_seconds", "30", FCVAR_NOTIFY)
CreateConVar("ttt_firstpreptime", "60")

local ttt_haste = CreateConVar("ttt_haste", "1", FCVAR_NOTIFY)
CreateConVar("ttt_haste_starting_minutes", "5", FCVAR_NOTIFY)
CreateConVar("ttt_haste_minutes_per_death", "0.5", FCVAR_NOTIFY)

CreateConVar("ttt_spawn_wave_interval", "0")

CreateConVar("ttt_traitor_pct", "0.25")
CreateConVar("ttt_traitor_max", "32")

CreateConVar("ttt_detective_pct", "0.13", FCVAR_NOTIFY)
CreateConVar("ttt_detective_max", "32")
CreateConVar("ttt_detective_min_players", "8")
CreateConVar("ttt_detective_karma_min", "600")


-- Traitor credits
CreateConVar("ttt_credits_starting", "2")
CreateConVar("ttt_credits_award_pct", "0.35")
CreateConVar("ttt_credits_award_size", "1")
CreateConVar("ttt_credits_award_repeat", "1")
CreateConVar("ttt_credits_detectivekill", "1")

CreateConVar("ttt_credits_alonebonus", "1")

-- Detective credits
CreateConVar("ttt_det_credits_starting", "1")
CreateConVar("ttt_det_credits_traitorkill", "0")
CreateConVar("ttt_det_credits_traitordead", "1")


CreateConVar("ttt_announce_deaths", "1", FCVAR_ARCHIVE + FCVAR_NOTIFY)

CreateConVar("ttt_use_weapon_spawn_scripts", "1")

CreateConVar("ttt_always_use_mapcycle", "0")

CreateConVar("ttt_round_limit", "6", FCVAR_ARCHIVE + FCVAR_NOTIFY + FCVAR_REPLICATED)
CreateConVar("ttt_time_limit_minutes", "75", FCVAR_NOTIFY + FCVAR_REPLICATED)

CreateConVar("ttt_idle_limit", "180", FCVAR_NOTIFY)

CreateConVar("ttt_voice_drain", "0", FCVAR_NOTIFY)
CreateConVar("ttt_voice_drain_normal", "0.2", FCVAR_NOTIFY)
CreateConVar("ttt_voice_drain_admin", "0.05", FCVAR_NOTIFY)
CreateConVar("ttt_voice_drain_recharge", "0.05", FCVAR_NOTIFY)

CreateConVar("ttt_namechange_kick", "1", FCVAR_NOTIFY)
CreateConVar("ttt_namechange_bantime", "10")

local ttt_detective = CreateConVar("ttt_sherlock_mode", "1", FCVAR_ARCHIVE + FCVAR_NOTIFY)
local ttt_minply = CreateConVar("ttt_minimum_players", "2", FCVAR_ARCHIVE + FCVAR_NOTIFY)

-- debuggery
local ttt_dbgwin = CreateConVar("ttt_debug_preventwin", "0")

-- Localise stuff we use often. It's like Lua go-faster stripes.
local math = math
local table = table
local umsg = umsg
local player = player
local timer = timer

---- Round mechanics
function GM:Initialize()
   MsgN("Trouble In Terrorist Town gamemode initializing...")
   ShowVersion()

   -- Force friendly fire to be enabled. If it is off, we do not get lag compensation.
   RunConsoleCommand("mp_friendlyfire", "1")

   -- Default crowbar unlocking settings, may be overridden by config entity
   GAMEMODE.crowbar_unlocks = {
      [OPEN_DOOR] = true,
      [OPEN_ROT] = true,
      [OPEN_BUT] = true,
      [OPEN_NOTOGGLE]= true
   };

   -- More map config ent defaults
   GAMEMODE.force_plymodel = ""
   GAMEMODE.propspec_allow_named = true

   GAMEMODE.MapWin = WIN_NONE
   GAMEMODE.AwardedCredits = false
   GAMEMODE.AwardedCreditsDead = 0

   GAMEMODE.round_state = ROUND_WAIT
   GAMEMODE.FirstRound = true
   GAMEMODE.RoundStartTime = 0

   GAMEMODE.DamageLog = {}
   GAMEMODE.LastRole = {}
   GAMEMODE.playermodel = GetRandomPlayerModel()
   GAMEMODE.playercolor = COLOR_WHITE

   -- Delay reading of cvars until config has definitely loaded
   GAMEMODE.cvar_init = false

   SetGlobalFloat("ttt_round_end", -1)
   SetGlobalFloat("ttt_haste_end", -1)

   -- For the paranoid
   math.randomseed(os.time())

   WaitForPlayers()

   if cvars.Number("sv_alltalk", 0) > 0 then
      ErrorNoHalt("TTT WARNING: sv_alltalk is enabled. Dead players will be able to talk to living players. TTT will now attempt to set sv_alltalk 0.
")
      RunConsoleCommand("sv_alltalk", "0")
   end

   local cstrike = false
   for _, g in pairs(engine.GetGames()) do
      if g.folder == 'cstrike' then cstrike = true end
   end
   if not cstrike then
      ErrorNoHalt("TTT WARNING: CS:S does not appear to be mounted by GMod. Things may break in strange ways. Server admin? Check the TTT readme for help.
")
   end

   GAMEMODE:CheckFileConsistency()
end

function GM:InitPostEntity()
   self.Customized = WEPS.HasCustomEquipment()

   self:UpdateServerTags()
end

-- Used to do this in Initialize, but server cfg has not always run yet by that
-- point.
function GM:InitCvars()
   MsgN("TTT initializing convar settings...")

   -- Initialize game state that is synced with client
   SetGlobalInt("ttt_rounds_left", GetConVar("ttt_round_limit"):GetInt())
   GAMEMODE:SyncGlobals()
   KARMA.InitState()

   self.cvar_init = true
end

function GM:GetGameDescription() return self.Name end

-- Convar replication is broken in gmod, so we do this.
-- I don't like it any more than you do, dear reader.
function GM:SyncGlobals()
   SetGlobalBool("ttt_detective", ttt_detective:GetBool())
   SetGlobalBool("ttt_haste", ttt_haste:GetBool())
   SetGlobalInt("ttt_time_limit_minutes", GetConVar("ttt_time_limit_minutes"):GetInt())
   SetGlobalBool("ttt_highlight_admins", GetConVar("ttt_highlight_admins"):GetBool())
   SetGlobalBool("ttt_locational_voice", GetConVar("ttt_locational_voice"):GetBool())
   SetGlobalInt("ttt_idle_limit", GetConVar("ttt_idle_limit"):GetInt())

   SetGlobalBool("ttt_voice_drain", GetConVar("ttt_voice_drain"):GetBool())
   SetGlobalFloat("ttt_voice_drain_normal", GetConVar("ttt_voice_drain_normal"):GetFloat())
   SetGlobalFloat("ttt_voice_drain_admin", GetConVar("ttt_voice_drain_admin"):GetFloat())
   SetGlobalFloat("ttt_voice_drain_recharge", GetConVar("ttt_voice_drain_recharge"):GetFloat())
end

function SendRoundState(state, ply)
   if ply then
      umsg.Start("round_state", ply)
   else
      umsg.Start("round_state")
   end
   umsg.Char(state)
   umsg.End()
end

-- Round state is encapsulated by set/get so that it can easily be changed to
-- eg. a networked var if this proves more convenient
function SetRoundState(state)
   GAMEMODE.round_state = state

   SCORE:RoundStateChange(state)

   SendRoundState(state)
end

function GetRoundState()
   return GAMEMODE.round_state
end

local function EnoughPlayers()
   local ready = 0
   -- only count truly available players, ie. no forced specs
   for _, ply in pairs(player.GetAll()) do
      if IsValid(ply) and ply:ShouldSpawn() then
         ready = ready + 1
      end
   end
   return ready >= ttt_minply:GetInt()
end

-- Used to be in Think/Tick, now in a timer
function WaitingForPlayersChecker()
   if GetRoundState() == ROUND_WAIT then
      if EnoughPlayers() then
         timer.Create("wait2prep", 1, 1, PrepareRound)

         timer.Stop("waitingforply")
      end
   end
end

-- Start waiting for players
function WaitForPlayers()
   SetRoundState(ROUND_WAIT)

   if not timer.Start("waitingforply") then
      timer.Create("waitingforply", 2, 0, WaitingForPlayersChecker)
   end
end

-- When a player initially spawns after mapload, everything is a bit strange;
-- just making him spectator for some reason does not work right. Therefore,
-- we regularly check for these broken spectators while we wait for players
-- and immediately fix them.
function FixSpectators()
   for k, ply in pairs(player.GetAll()) do
      if ply:IsSpec() and not ply:GetRagdollSpec() and ply:GetMoveType() < MOVETYPE_NOCLIP then
         ply:Spectate(OBS_MODE_ROAMING)
      end
   end
end

-- Used to be in think, now a timer
local function WinChecker()
   if GetRoundState() == ROUND_ACTIVE then
      if CurTime() > GetGlobalFloat("ttt_round_end", 0) then
         EndRound(WIN_TIMELIMIT)
      else
         local win = CheckForWin()
         if win != WIN_NONE then
            EndRound(win)
            return
         end
      end
   end
end

local function NameChangeKick()
   if not GetConVar("ttt_namechange_kick"):GetBool() then
      timer.Destroy("namecheck")
      return
   end

   if GetRoundState() == ROUND_ACTIVE then
      for _, ply in pairs(player.GetHumans()) do
         if ply.spawn_nick then
            if ply.has_spawned and ply.spawn_nick != ply:Nick() then
               local t = GetConVar("ttt_namechange_bantime"):GetInt()
               local msg = "Changed name during a round"
               if t > 0 then
                  ply:KickBan(t, msg)
               else
                  ply:Kick(msg)
               end
            end
         else
            ply.spawn_nick = ply:Nick()
         end
      end
   end
end

function StartNameChangeChecks()
   if not GetConVar("ttt_namechange_kick"):GetBool() then return end

   -- bring nicks up to date, may have been changed during prep/post
   for _, ply in pairs(player.GetAll()) do
      ply.spawn_nick = ply:Nick()
   end

   if not timer.Exists("namecheck") then
      timer.Create("namecheck", 3, 0, NameChangeKick)
   end
end

function StartWinChecks()
   if not timer.Start("winchecker") then
      timer.Create("winchecker", 1, 0, WinChecker)
   end
end

function StopWinChecks()
   timer.Stop("winchecker")
end

local function CleanUp()
   local et = ents.TTT
   -- if we are going to import entities, it's no use replacing HL2DM ones as
   -- soon as they spawn, because they'll be removed anyway
   et.SetReplaceChecking(not et.CanImportEntities(game.GetMap()))

   et.FixParentedPreCleanup()

   game.CleanUpMap()

   et.FixParentedPostCleanup()

   -- Strip players now, so that their weapons are not seen by ReplaceEntities
   for k,v in pairs(player.GetAll()) do
      if IsValid(v) then
         v:StripWeapons()
      end
   end

   -- a different kind of cleanup
   util.UnbridledHateForULX()
end

local function SpawnEntities()
   local et = ents.TTT
   -- Spawn weapons from script if there is one
   local import = et.CanImportEntities(game.GetMap())

   if import then
      et.ProcessImportScript(game.GetMap())
   else
      -- Replace HL2DM/ZM ammo/weps with our own
      et.ReplaceEntities()

      -- Populate CS:S/TF2 maps with extra guns
      et.PlaceExtraWeapons()
   end

   -- Finally, get players in there
   SpawnWillingPlayers()
end


local function StopRoundTimers()
   -- remove all timers
   timer.Stop("wait2prep")
   timer.Stop("prep2begin")
   timer.Stop("end2begin")
   timer.Stop("winchecker")
end

-- Make sure we have the players to do a round, people can leave during our
-- preparations so we'll call this numerous times
local function CheckForAbort()
   if not EnoughPlayers() then
      LANG.Msg("round_minplayers")
      StopRoundTimers()

      WaitForPlayers()
      return true
   end

   return false
end

function GM:TTTDelayRoundStartForVote()
   -- No voting system available in GM13 (yet)
   --return self:InGamemodeVote()
   return false
end

function PrepareRound()
   GAMEMODE:UpdateServerTags()

   -- Check playercount
   if CheckForAbort() then return end

   if GetGlobalBool("InContinueVote", false) then
      GAMEMODE:FinishContinueVote() -- may start a gamemode vote
   end

   if hook.Call("TTTDelayRoundStartForVote", GAMEMODE) then
      LANG.Msg("round_voting", {num = 30})

      timer.Create("delayedprep", 30, 1, PrepareRound)
      return
   end

   -- Cleanup
   CleanUp()

   GAMEMODE.MapWin = WIN_NONE
   GAMEMODE.AwardedCredits = false
   GAMEMODE.AwardedCreditsDead = 0

   SCORE:Reset()

   -- Update damage scaling
   KARMA.RoundBegin()

   -- New look. Random if no forced model set.
   GAMEMODE.playermodel = GAMEMODE.force_plymodel == "" and GetRandomPlayerModel() or GAMEMODE.force_plymodel
   GAMEMODE.playercolor = hook.Call("TTTPlayerColor", GAMEMODE, GAMEMODE.playermodel)

   if CheckForAbort() then return end

   -- Schedule round start
   local ptime = GetConVar("ttt_preptime_seconds"):GetInt()
   if GAMEMODE.FirstRound then
      ptime = GetConVar("ttt_firstpreptime"):GetInt()
      GAMEMODE.FirstRound = false
   end

   -- Piggyback on "round end" time global var to show end of phase timer
   SetRoundEnd(CurTime() + ptime)

   timer.Create("prep2begin", ptime, 1, BeginRound)

   -- Mute for a second around traitor selection, to counter a dumb exploit
   -- related to traitor's mics cutting off for a second when they're selected.
   timer.Create("selectmute", ptime - 1, 1, function() MuteForRestart(true) end)

   LANG.Msg("round_begintime", {num = ptime})
   SetRoundState(ROUND_PREP)

   -- Delay spawning until next frame to avoid ent overload
   timer.Simple(0.01, SpawnEntities)

   -- Undo the roundrestart mute, though they will once again be muted for the
   -- selectmute timer.
   timer.Create("restartmute", 1, 1, function() MuteForRestart(false) end)

   SendUserMessage("clearclientstate")

   -- In case client's cleanup fails, make client set all players to innocent role
   timer.Simple(1, SendRoleReset)

   -- Tell hooks and map we started prep
   hook.Call("TTTPrepareRound")

   ents.TTT.TriggerRoundStateOutputs(ROUND_PREP)
end

function SetRoundEnd(endtime)
   SetGlobalFloat("ttt_round_end", endtime)
end

function IncRoundEnd(incr)
   SetRoundEnd(GetGlobalFloat("ttt_round_end", 0) + incr)
end

function TellTraitorsAboutTraitors()
   local traitornicks = {}
   for k,v in pairs(player.GetAll()) do
      if v:IsTraitor() then
         table.insert(traitornicks, v:Nick())
      end
   end

   -- This is ugly as hell, but it's kinda nice to filter out the names of the
   -- traitors themselves in the messages to them
   for k,v in pairs(player.GetAll()) do
      if v:IsTraitor() then
         if #traitornicks < 2 then
            LANG.Msg(v, "round_traitors_one")
            return
         else
            local names = ""
            for i,name in pairs(traitornicks) do
               if name != v:Nick() then
                  names = names .. name .. ", "
               end
            end
            names = string.sub(names, 1, -3)
            LANG.Msg(v, "round_traitors_more", {names = names})
         end
      end
   end
end


function SpawnWillingPlayers(dead_only)
   local plys = player.GetAll()
   local wave_delay = GetConVar("ttt_spawn_wave_interval"):GetFloat()

   -- simple method, should make this a case of the other method once that has
   -- been tested.
   if wave_delay <= 0 or dead_only then
      for k, ply in pairs(player.GetAll()) do
         if IsValid(ply) then
            ply:SpawnForRound(dead_only)
         end
      end
   else
      -- wave method
      local num_spawns = #GetSpawnEnts()

      local to_spawn = {}
      for _, ply in RandomPairs(plys) do
         if IsValid(ply) and ply:ShouldSpawn() then
            table.insert(to_spawn, ply)
            GAMEMODE:PlayerSpawnAsSpectator(ply)
         end
      end

      local sfn = function()
                     local c = 0
                     -- fill the available spawnpoints with players that need
                     -- spawning
                     while c < num_spawns and #to_spawn > 0 do
                        for k, ply in pairs(to_spawn) do
                           if IsValid(ply) then
                              if ply:SpawnForRound() then
                                 -- a spawn ent is now occupied
                                 c = c + 1
                              end
                           end
                           -- Few possible cases:
                           -- 1) player has now been spawned
                           -- 2) player should remain spectator after all
                           -- 3) player has disconnected
                           -- In all cases we don't need to spawn them again.
                           table.remove(to_spawn, k)

                           -- all spawn ents are occupied, so the rest will have
                           -- to wait for next wave
                           if c >= num_spawns then
                              break
                           end
                        end
                     end

                     MsgN("Spawned " .. c .. " players in spawn wave.")

                     if #to_spawn == 0 then
                        timer.Destroy("spawnwave")
                        MsgN("Spawn waves ending, all players spawned.")
                     end
                  end

      MsgN("Spawn waves starting.")
      timer.Create("spawnwave", wave_delay, 0, sfn)

      -- already run one wave, which may stop the timer if everyone is spawned
      -- in one go
      sfn()
   end
end

local function InitRoundEndTime()
   -- Init round values
   local endtime = CurTime() + (GetConVar("ttt_roundtime_minutes"):GetInt() * 60)
   if HasteMode() then
      endtime = CurTime() + (GetConVar("ttt_haste_starting_minutes"):GetInt() * 60)
      -- this is a "fake" time shown to innocents, showing the end time if no
      -- one would have been killed, it has no gameplay effect
      SetGlobalFloat("ttt_haste_end", endtime)
   end

   SetRoundEnd(endtime)
end

function BeginRound()
   GAMEMODE:SyncGlobals()

   if CheckForAbort() then return end

   AnnounceVersion()

   InitRoundEndTime()

   if CheckForAbort() then return end

   -- Respawn dumb people who died during prep
   SpawnWillingPlayers(true)

   -- Remove their ragdolls
   ents.TTT.RemoveRagdolls(true)

   WEPS.ForcePrecache()

   if CheckForAbort() then return end

   -- Select traitors & co. This is where things really start so we can't abort
   -- anymore.
   SelectRoles()
   LANG.Msg("round_selected")
   SendFullStateUpdate()

   -- Edge case where a player joins just as the round starts and is picked as
   -- traitor, but for whatever reason does not get the traitor state msg. So
   -- re-send after a second just to make sure everyone is getting it.
   timer.Simple(1, SendFullStateUpdate)

   SCORE:HandleSelection() -- log traitors and detectives

   -- Give the StateUpdate messages ample time to arrive
   timer.Simple(1.5, TellTraitorsAboutTraitors)
   timer.Simple(2.5, ShowRoundStartPopup)

   -- Start the win condition check timer
   StartWinChecks()
   StartNameChangeChecks()
   timer.Create("selectmute", 1, 1, function() MuteForRestart(false) end)

   GAMEMODE.DamageLog = {}
   GAMEMODE.RoundStartTime = CurTime()

   -- Sound start alarm
   SetRoundState(ROUND_ACTIVE)
   LANG.Msg("round_started")
   ServerLog("Round proper has begun...
")

   GAMEMODE:UpdatePlayerLoadouts() -- needs to happen when round_active

   hook.Call("TTTBeginRound")

   ents.TTT.TriggerRoundStateOutputs(ROUND_BEGIN)
end

function PrintResultMessage(type)
   ServerLog("Round ended.
")
   if type == WIN_TIMELIMIT then
      LANG.Msg("win_time")
      ServerLog("Result: timelimit reached, traitors lose.
")
   elseif type == WIN_TRAITOR then
      LANG.Msg("win_traitor")
      ServerLog("Result: traitors win.
")
   elseif type == WIN_INNOCENT then
      LANG.Msg("win_innocent")
      ServerLog("Result: innocent win.
")
   else
      ServerLog("Result: unknown victory condition!
")
   end
end

local function ShouldMapSwitch()
   return true -- no voting until fretta replacement arrives
--   return GetConVar("ttt_always_use_mapcycle"):GetBool()
end

function CheckForMapSwitch()
   -- Check for mapswitch
   local rounds_left = math.max(0, GetGlobalInt("ttt_rounds_left", 6) - 1)
   SetGlobalInt("ttt_rounds_left", rounds_left)

   local time_left = math.max(0, (GetConVar("ttt_time_limit_minutes"):GetInt() * 60) - CurTime())
   local switchmap = false
   local nextmap = string.upper(game.GetMapNext())

   if ShouldMapSwitch() then
      if rounds_left <= 0 then
         LANG.Msg("limit_round", {mapname = nextmap})
         switchmap = true
      elseif time_left <= 0 then
         LANG.Msg("limit_time", {mapname = nextmap})
         switchmap = true
      end
   else
      -- we only get here if fretta_voting is on and always_use_mapcycle off
      if rounds_left <= 0 or time_left <= 0 then
         LANG.Msg("limit_vote")

         -- pending fretta replacement...
         switchmap = true
         --GAMEMODE:StartFrettaVote()
      end
   end

   if switchmap then
      timer.Stop("end2prep")
      timer.Simple(15, game.LoadNextMap)
   elseif ShouldMapSwitch() then
      LANG.Msg("limit_left", {num = rounds_left,
                              time = math.ceil(time_left / 60),
                              mapname = nextmap})
   end
end

function EndRound(type)
   PrintResultMessage(type)

   -- first handle round end
   SetRoundState(ROUND_POST)

   local ptime = math.max(5, GetConVar("ttt_posttime_seconds"):GetInt())
   LANG.Msg("win_showreport", {num = ptime})
   timer.Create("end2prep", ptime, 1, PrepareRound)

   -- Piggyback on "round end" time global var to show end of phase timer
   SetRoundEnd(CurTime() + ptime)

   timer.Create("restartmute", ptime - 1, 1, function() MuteForRestart(true) end)

   -- Stop checking for wins
   StopWinChecks()

   -- We may need to start a timer for a mapswitch, or start a vote
   CheckForMapSwitch()

   -- Show unobtrusive vote window (only if fretta voting enabled and only if
   -- not already in a round/time limit induced vote)
   --if not GAMEMODE:InGamemodeVote() then
   --   GAMEMODE:StartContinueVote()
   --end

   KARMA.RoundEnd()

   -- now handle potentially error prone scoring stuff

   -- register an end of round event
   SCORE:RoundComplete(type)

   -- update player scores
   SCORE:ApplyEventLogScores(type)

   -- send the clients the round log, players will be shown the report
   SCORE:StreamToClients()

   -- server plugins might want to start a map vote here or something
   -- these hooks are not used by TTT internally
   hook.Call("TTTEndRound", GAMEMODE, type)

   ents.TTT.TriggerRoundStateOutputs(ROUND_POST, type)
end

function GM:MapTriggeredEnd(wintype)
   self.MapWin = wintype
end

-- The most basic win check is whether both sides have one dude alive
function CheckForWin()
   if ttt_dbgwin:GetBool() then return WIN_NONE end

   if GAMEMODE.MapWin == WIN_TRAITOR or GAMEMODE.MapWin == WIN_INNOCENT then
      local mw = GAMEMODE.MapWin
      GAMEMODE.MapWin = WIN_NONE
      return mw
   end

   local traitor_alive = false
   local innocent_alive = false
   for k,v in pairs(player.GetAll()) do
      if v:Alive() and v:IsTerror() then
         if v:GetTraitor() then
            traitor_alive = true
         else
            innocent_alive = true
         end
      end

      if traitor_alive and innocent_alive then
         return WIN_NONE --early out
      end
   end

   if traitor_alive and not innocent_alive then
      return WIN_TRAITOR
   elseif not traitor_alive and innocent_alive then
      return WIN_INNOCENT
   elseif not innocent_alive then
      -- ultimately if no one is alive, traitors win
      return WIN_TRAITOR
   end

   return WIN_NONE
end

local function GetTraitorCount(ply_count)
   -- get number of traitors: pct of players rounded down
   local traitor_count = math.floor(ply_count * GetConVar("ttt_traitor_pct"):GetFloat())
   -- make sure there is at least 1 traitor
   traitor_count = math.Clamp(traitor_count, 1, GetConVar("ttt_traitor_max"):GetInt())

   return traitor_count
end


local function GetDetectiveCount(ply_count)
   if ply_count < GetConVar("ttt_detective_min_players"):GetInt() then return 0 end

   local det_count = math.floor(ply_count * GetConVar("ttt_detective_pct"):GetFloat())
   -- limit to a max
   det_count = math.Clamp(det_count, 1, GetConVar("ttt_detective_max"):GetInt())

   return det_count
end


function SelectRoles()
   local choices = {}
   local prev_roles = {
      [ROLE_INNOCENT] = {},
      [ROLE_TRAITOR] = {},
      [ROLE_DETECTIVE] = {}
   };

   if not GAMEMODE.LastRole then GAMEMODE.LastRole = {} end

   for k,v in pairs(player.GetAll()) do
      -- everyone on the spec team is in specmode
      if IsValid(v) and (not v:IsSpec()) then
         -- save previous role and sign up as possible traitor/detective

         local r = GAMEMODE.LastRole[v:UniqueID()] or v:GetRole() or ROLE_INNOCENT

         table.insert(prev_roles[r], v)

         table.insert(choices, v)
      end

      v:SetRole(ROLE_INNOCENT)
   end

   -- determine how many of each role we want
   local choice_count = #choices
   local traitor_count = GetTraitorCount(choice_count)
   local det_count = GetDetectiveCount(choice_count)

   if choice_count == 0 then return end

   -- first select traitors
   local ts = 0
   while ts < traitor_count do
      -- select random index in choices table
      local pick = math.random(1, #choices)

      -- the player we consider
      local pply = choices[pick]

      -- make this guy traitor if he was not a traitor last time, or if he makes
      -- a roll
      if IsValid(pply) and
         ((not table.HasValue(prev_roles[ROLE_TRAITOR], pply)) or (math.random(1, 3) == 2)) then
         pply:SetRole(ROLE_TRAITOR)

         table.remove(choices, pick)
         ts = ts + 1
      end
   end

   -- now select detectives, explicitly choosing from players who did not get
   -- traitor, so becoming detective does not mean you lost a chance to be
   -- traitor
   local ds = 0
   local min_karma = GetConVarNumber("ttt_detective_min_karma") or 0
   while (ds < det_count) and (#choices >= 1) do

      -- sometimes we need all remaining choices to be detective to fill the
      -- roles up, this happens more often with a lot of detective-deniers
      if #choices <= (det_count - ds) then
         for k, pply in pairs(choices) do
            if IsValid(pply) then
               pply:SetRole(ROLE_DETECTIVE)
            end
         end

         break -- out of while
      end


      local pick = math.random(1, #choices)
      local pply = choices[pick]

      -- we are less likely to be a detective unless we were innocent last round
      if (IsValid(pply) and
          ((pply:GetBaseKarma() > min_karma and
           table.HasValue(prev_roles[ROLE_INNOCENT], pply)) or
           math.random(1,3) == 2)) then

         -- if a player has specified he does not want to be detective, we skip
         -- him here (he might still get it if we don't have enough
         -- alternatives)
         if not pply:GetAvoidDetective() then
            pply:SetRole(ROLE_DETECTIVE)
            ds = ds + 1
         end

         table.remove(choices, pick)
      end
   end

   GAMEMODE.LastRole = {}

   for _, ply in pairs(player.GetAll()) do
      -- initialize credit count for everyone based on their role
      ply:SetDefaultCredits()

      -- store a uid -> role map
      GAMEMODE.LastRole[ply:UniqueID()] = ply:GetRole()
   end
end


local function ForceRoundRestart(ply, command, args)
   -- ply is nil on dedicated server console
   if (not IsValid(ply)) or ply:IsAdmin() or ply:IsSuperAdmin() or cvars.Bool("sv_cheats", 0) then
      LANG.Msg("round_restart")

      StopRoundTimers()

      -- do prep
      PrepareRound()
   else
      ply:PrintMessage(HUD_PRINTCONSOLE, "You must be a GMod Admin or SuperAdmin on the server to use this command, or sv_cheats must be enabled.")
   end
end
concommand.Add("ttt_roundrestart", ForceRoundRestart)

-- Version announce also used in Initialize
function ShowVersion(ply)
   local text = Format("This is TTT version %s
", GAMEMODE.Version)
   if IsValid(ply) then
      ply:PrintMessage(HUD_PRINTNOTIFY, text)
   else
      Msg(text)
   end
end
concommand.Add("ttt_version", ShowVersion)

function AnnounceVersion()
   local text = Format("You are playing %s, version %s.
", GAMEMODE.Name, GAMEMODE.Version)

   -- announce to players
   for k, ply in pairs(player.GetAll()) do
      if IsValid(ply) then
         ply:PrintMessage(HUD_PRINTTALK, text)
      end
   end
end


[editline]26th January 2014[/editline]

Edit~ I fixed it. But I used the DNA scanner from detailed events addon. It doesn’t show up in the normal damagelogs. Is there a fix for it to show? here’s the scanner’s code


-- DNA Scanner

if SERVER then
   AddCSLuaFile( "shared.lua" )
end


SWEP.HoldType = "normal"

if CLIENT then
   SWEP.PrintName = "dna_name"
   SWEP.Slot = 8

   SWEP.ViewModelFOV = 10

   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.DrawCrosshair       = false
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.LimitedStock = true

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.Owner
      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.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay )

   -- will be tracing against players
   self.Owner:LagCompensation(true)

   local spos = self.Owner:GetShootPos()
   local sdest = spos + (self.Owner:GetAimVector() * self.Range)

   local tr = util.TraceLine({start=spos, endpos=sdest, filter=self.Owner, mask=MASK_SHOT})
   local ent = tr.Entity
   if IsValid(ent) and (not ent:IsPlayer()) then
      if SERVER then
         if ent:IsPlayer() then
            --self:GatherPlayerSample(ent)
         elseif 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.Owner:EmitSound(beep_miss)
      end
   end

   self.Owner: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_uid then
      ply = player.GetByUniqueID(sample.killer_uid)
   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.Owner, 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)

		 local role = "???"
		 if corpse.was_role == ROLE_TRAITOR then
			role = "traitor"
		 elseif corpse.was_role == ROLE_INNOCENT then
			role = "innocent"
		 elseif corpse.was_role == ROLE_DETECTIVE then
			role = "detective"
		 end
		 AddToDamageLog({DMG_LOG.DNA_BODY, self.Owner:Nick(), self.Owner:GetRoleString(), (IsValid(killer) and killer:Nick() or "<disconnected>"), killer:GetRoleString(), CORPSE.GetPlayerNick(corpse) or "<invalid>", role, {self.Owner:SteamID(), killer:SteamID() or ""}})
         DamageLog("SAMPLE:	 " .. self.Owner: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.Owner, 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.Owner then
            own = own + 1
         elseif table.HasTable(self.ItemSamples, prnt) then
            old = old + 1
         else
            table.insert(self.ItemSamples, prnt)

			AddToDamageLog({DMG_LOG.DNA_OBJECT, self.Owner:Nick(), self.Owner:GetRoleString(), (IsValid(p) and p:Nick() or "<disconnected>"), p:GetRoleString(), ent:GetClass(), {self.Owner:SteamID(), p:SteamID() or ""}})
			
            DamageLog("SAMPLE:	 " .. self.Owner:Nick() .. " retrieved DNA of " .. (IsValid(p) and p:Nick() or "<disconnected>") .. " from " .. ent:GetClass())

            new = new + 1
            hook.Call("TTTFoundDNA", GAMEMODE, self.Owner, p, ent)
         end
      end
      return new, old, own
   end
   return -1
end

function SWEP:RemovePlayerSample(idx)
   if self.PlayerSamples[idx] then
      table.remove(self.PlayerSamples, idx)
      
      self:SendPrints(false)
   end
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.Weapon: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)
      umsg.Start("showprints", self.Owner)
      umsg.Bool(should_open)

      umsg.Char(#self.ItemSamples)
      for k,v in ipairs(self.ItemSamples) do
         umsg.String(v.cls)
      end

      umsg.End()
   end

   function SWEP:SendScan(pos)
      local clear = (pos == nil) or (not IsValid(self.Owner))
      umsg.Start("scanresult", self.Owner)
      umsg.Bool(clear)
      if not clear then
         umsg.Vector(pos)
      end
      umsg.End()
   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.Owner)) 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.Owner: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.Owner) 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
   function SWEP:DrawHUD()
      self:DrawHelp()

      local spos = self.Owner:GetShootPos()
      local sdest = spos + (self.Owner:GetAimVector() * self.Range)

      local tr = util.TraceLine({start=spos, endpos=sdest, filter=self.Owner, 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("TYPE: " .. (ent:GetClass() == "prop_ragdoll" and "BODY" or "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 T = LANG.GetTranslation
   local PT = LANG.GetParamTranslation
   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 = 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("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(um)
      local should_open = um:ReadBool()

      local num = um:ReadChar()
      local item_prints = {}
      for i=1, num do
         local ent = um:ReadString()
         table.insert(item_prints, ent)
      end

      if should_open then
         if ValidPanel(printspanel) then
            printspanel:Remove()
         end

         local tester = GetTester(LocalPlayer())

         printspanel = ShowPrintsPopup(item_prints, tester)
      else
         if ValidPanel(printspanel) then
            printspanel:UpdatePrints(item_prints)
         end
      end
   end
   usermessage.Hook("showprints", RecvPrints)

   local beep_success = Sound("buttons/blip2.wav")
   --local beep_fail = Sound("buttons/button11.wav")
   local function RecvScan(um)
      local clear = um:ReadBool()
      if clear then
         RADAR.samples = {}
         RADAR.samples_count = 0
         return
      end

      local target_pos = um:ReadVector()
      if not target_pos then return end

      RADAR.samples = {
         {pos = target_pos}
      };

      RADAR.samples_count = 1

      surface.PlaySound(beep_success)
   end
   usermessage.Hook("scanresult", RecvScan)

   function SWEP:ClosePrintsPanel()
      if ValidPanel(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.Owner) then
      self.Owner.scanner_weapon = nil
   end
end

function SWEP:Reload()
   return false
end

function SWEP:Deploy()
   if SERVER and IsValid(self.Owner) then
      self.Owner:DrawViewModel(false)
      self.Owner.scanner_weapon = self.Weapon
   end
   return true
end

if CLIENT then
   function SWEP:DrawWorldModel()
      if not IsValid(self.Owner) then
         self:DrawModel()
      end
   end
end