Ctd in TTT-resource - no info on the cause

Hey there,

fairly new dev here. I’ve been trying to expand on a TTT-weapon called Dancegun which I expanded with available music and a reload-function to choose the song.

Original resource is most likely the following (multiple resources available, seem to be going back to this one):

My expansion:

Now I’d like to display the songtext on the unfortunate victims screen (rule on my TTT-server is that the victim may only sing along with the song and not say anything else).
This causes garrys mod to crash randomly, after several uses of the weapon. It’s something I can’t reproduce reliably, only thing I know is it has to be related to the HUD-code as everything else works without crashes.

Following code is a minimal example, full source code is shared in a spoiler below. Any ideas what I could do to track the error? Thanks for your time!

local SongTexts = {
  {
    {"You can dance", 5.0},
    {"I know you can dance", 1.0},
    {"Yup, it's working quite nicely", 5.0},
    {"Can confirm, it's working as intended", 1.0},
    {"*Song is ending*", 5.0}
  },
  {

  },
}

function PartyGunDisplayTextMain(ent, rndMeme) --ent is selected via bullet.Callback, rndMeme is an integer
  ent:SetNWInt('PartyGunText', 2)
  local timingDelay = SongTexts[1][1][2] -- replace first 1 with rndMeme
  local TimerAmount = #SongTexts[1] - 2 -- replace 1 with rndMeme

  local CurrLine = SongTexts[1][1][1] -- replace first 1 with rndMeme
  local NextLine = SongTexts[1][2][1] -- replace first 1 with rndMeme
  local NetTable = {CurrLine, NextLine}
  net.Start("SongTextMessage")
  net.WriteTable(NetTable)
  net.Send(ent)

  timer.Create('displayText', timingDelay, TimerAmount, function()
    local textCounter = ent:GetNWInt('PartyGunText')
    local CurrLine = SongTexts[1][textCounter][1] -- replace first 1 with rndMeme
    local NextLine = SongTexts[1][textCounter+1][1] -- replace first 1 with rndMeme
    local NetTable = {CurrLine, NextLine}
    net.Start("SongTextMessage")
    net.WriteTable(NetTable)
		net.Send(ent)

    timingDelay = SongTexts[1][textCounter][2]
    timer.Adjust('displayText', timingDelay)
    textCounter = textCounter + 1
    ent:SetNWInt('PartyGunText', textCounter)
  end)

end

if SERVER then

else
  net.Receive("SongTextMessage", function()
    hook.Remove("HUDPaint", "PartyGunHUDStarter")
    local Table = net.ReadTable()
    local CurrLine = Table[1]
    local NextLine = Table[2]
    hook.Add("HUDPaint", "PartyGunHUDStarter", function()
      surface.CreateFont("PartyGunHudFont", {
        font = "Roboto BK",
        extended = false,
        size = 48,
        weight = 500,
      })
      surface.SetFont("PartyGunHudFont")
      local cwidth, cheight = surface.GetTextSize(CurrLine)
      local nwidth, nheight = surface.GetTextSize(NextLine)
      local CenterW = ScrW()*0.5
      local cCenterH = ScrH()*0.25
      local nCenterH = cCenterH + 64
      local RectBorder = 16 --pt
      surface.SetTextColor(255, 0, 0, 255)
      surface.SetTextPos(CenterW-(cwidth/2), cCenterH-(cheight/2))
      surface.DrawText(CurrLine)
      surface.SetTextColor(180, 180, 180, 255)
      surface.SetTextPos(CenterW-(nwidth/2), nCenterH-(nheight/2))
      surface.DrawText(NextLine)
      local width = cwidth
      local height = cheight+nheight+64
      local CenterH = (cCenterH+nCenterH)/2
      if nwidth > cwidth then
        width = nwidth
      end
      surface.SetDrawColor(0,0,0,100)
      surface.DrawRect(CenterW-(width/2)-RectBorder, CenterH-(height/2)-RectBorder, width+(2*RectBorder), height+(2*RectBorder) )
    end)
    if NextLine == "*Song is ending*" then
      timer.Remove("displayText")
      timer.Simple(5.0, function()
        hook.Remove("HUDPaint", "PartyGunHUDStarter")
      end)
    end
  end)
end

Full Source Code
SWEP.HoldType              = "pistol"

if CLIENT then
   SWEP.PrintName          = "PartyGun"
   SWEP.Slot               = 6

   SWEP.ViewModelFlip      = false
   SWEP.ViewModelFOV       = 54

   SWEP.EquipMenuData = {
      type = "item_weapon",
      desc = "Let's dance!"
   };

   SWEP.Icon = "vgui/ttt/danceicon.png"
end

SWEP.Base                  = "weapon_tttbase"

SWEP.Kind                  = WEAPON_PISTOL
SWEP.WeaponID              = AMMO_PISTOL

SWEP.Primary.Recoil        = 3
SWEP.Primary.Damage        = 1
SWEP.Primary.Delay         = 1
SWEP.Primary.Cone          = 0.01
SWEP.Primary.ClipSize      = 2
SWEP.Primary.Automatic     = false
SWEP.Primary.DefaultClip   = 2
SWEP.Primary.ClipMax       = 2
SWEP.Primary.Ammo          = "none"
SWEP.AmmoEnt               = "none"

SWEP.UseHands              = true
SWEP.ViewModel             = "models/weapons/cstrike/c_pist_fiveseven.mdl"
SWEP.WorldModel            = "models/weapons/w_pist_fiveseven.mdl"

SWEP.Kind = WEAPON_EQUIP1
SWEP.CanBuy = {ROLE_TRAITOR}
SWEP.LimitedStock = true

SWEP.IronSightsPos         = Vector(-5.95, -1, 4.799)
SWEP.IronSightsAng         = Vector(0, 0, 0)

local NoMemes = 12
local rndMeme = 0
local textCounter = 0
local delay = 0.6
local lastOccurance = -delay

if SERVER then
	util.AddNetworkString("PartyGunMessage")
  util.AddNetworkString("SongTextMessage")
end

local MemeSongs = {
	"Mando Diao Dance with Somebody",
	"I don't feel like dancing",
	"Rick Astley",
	"I like to move it",
	"September",
	"Whitney Houston Dance with Somebody",
	"Dancing Queen",
	"I had the time of my Life",
  "How could this happen to me",
  "Trololo",
  "Gangnam Style",
  "Safety Dance",
}

function SWEP:Reload()
	if self.Owner:GetActiveWeapon():GetClass() == 'weapon_ttt_partygun' then

		local timeElapsed = CurTime() - lastOccurance
		if timeElapsed < delay then

		else
			lastOccurance = CurTime()
			self.rndMeme = self.rndMeme + 1
			if self.rndMeme > NoMemes then
				self.rndMeme = 1
			end

			if SERVER then
				net.Start("PartyGunMessage")
				net.WriteString(MemeSongs[self.rndMeme])
				net.Send(self.Owner)
			end
		end

	end
end

function SWEP:Initialize()
		self.rndMeme = math.random(1,NoMemes)
end

function SWEP:Deploy()
	if SERVER then
		net.Start("PartyGunMessage")
		net.WriteString(MemeSongs[self.rndMeme])
		net.Send(self.Owner)
	end
end

function SWEP:PrimaryAttack()
   if not self:CanPrimaryAttack() then return end
   --self.Owner:EmitSound("furz.wav")
   local cone = self.Primary.Cone
   local num = 1

   local bullet = {}
   bullet.Num    = num
   bullet.Src    = self.Owner:GetShootPos()
   bullet.Dir    = self.Owner:GetAimVector()
   bullet.Spread = Vector( cone, cone, 0 )
   bullet.Tracer = 1
   bullet.Force	= 10
   bullet.Damage = 1
   bullet.TracerName = "PhyscannonImpact"

   bullet.Callback = function(att, tr)
    if SERVER or (CLIENT and IsFirstTimePredicted()) then
       local ent = tr.Entity
          if SERVER and ent:IsPlayer() then
            -- build in check that prohibits the weapon to hit multiple times
  					ent:EmitSound("partygun"..tostring(self.rndMeme)..".mp3")
            self:TakePrimaryAmmo( 1 )
  					ent:GodEnable()
            PartyGunDisplayTextMain(ent, self.rndMeme)
  					local timerName = "reDance" .. math.random(1,10000)
  					timer.Create( timerName, 1, 15, function()
  					  local danceChange = math.random(1, 2)
  					  if danceChange == 1 then
  					    ent:DoAnimationEvent( ACT_GMOD_GESTURE_TAUNT_ZOMBIE, 1641 )
  					  else
  					    ent:DoAnimationEvent( ACT_GMOD_TAUNT_DANCE, 1642 )
  					  end
  					  if !ent:IsFrozen() then ent:Freeze(true) end
  					end)
  					ent:Freeze(true)
  					timer.Simple( 15, function()
  						if ent:Alive() then
  						ent:GodDisable()
  						ent:Freeze(false)
  						local totalHealth = ent:Health()
  						local inflictWep = ents.Create('weapon_ttt_partygun')
  						ent:TakeDamage( 200, att, inflictWep )
  						timer.Simple( 2, function() if ent:IsFrozen() then ent:Freeze(false) end end)
  						end
  					end)

          end
       end
    end
   self.Owner:FireBullets( bullet )
   if SERVER then
     self:TakePrimaryAmmo( 1 )
   end
end

local SongTexts = {
  {
    {"You can dance", 5.0},
    {"I know you can dance", 1.0},
    {"Yup, it's working quite nicely", 5.0},
    {"Can confirm, it's working as intended", 1.0},
    {"*Song is ending*", 5.0}
  },
  {

  },
}

function PartyGunDisplayTextMain(ent, rndMeme)
  ent:SetNWInt('PartyGunText', 2)
  local timingDelay = SongTexts[1][1][2] -- replace first 1 with rndMeme
  local TimerAmount = #SongTexts[1] - 2 -- replace 1 with rndMeme

  local CurrLine = SongTexts[1][1][1] -- replace first 1 with rndMeme
  local NextLine = SongTexts[1][2][1] -- replace first 1 with rndMeme
  local NetTable = {CurrLine, NextLine}
  net.Start("SongTextMessage")
  net.WriteTable(NetTable)
  net.Send(ent)

  timer.Create('displayText', timingDelay, TimerAmount, function()
    local textCounter = ent:GetNWInt('PartyGunText')
    local CurrLine = SongTexts[1][textCounter][1] -- replace first 1 with rndMeme
    local NextLine = SongTexts[1][textCounter+1][1] -- replace first 1 with rndMeme
    local NetTable = {CurrLine, NextLine}
    net.Start("SongTextMessage")
    net.WriteTable(NetTable)
		net.Send(ent)

    timingDelay = SongTexts[1][textCounter][2]
    timer.Adjust('displayText', timingDelay)
    textCounter = textCounter + 1
    ent:SetNWInt('PartyGunText', textCounter)
  end)

end

function SWEP:OnDrop()
	self:Remove()
end

if SERVER then

else
	net.Receive("PartyGunMessage", function()
		local String = net.ReadString()
		chat.AddText(Color(255,255,255), "It's a dance off! Kill your enemy to the sounds of ", Color(255,0,0), String, Color(255,255,255), "!")
	end)
  net.Receive("SongTextMessage", function()
    hook.Remove("HUDPaint", "PartyGunHUDStarter")
    local Table = net.ReadTable()
    local CurrLine = Table[1]
    local NextLine = Table[2]
    hook.Add("HUDPaint", "PartyGunHUDStarter", function()
      surface.CreateFont("PartyGunHudFont", {
        font = "Roboto BK",
        extended = false,
        size = 48,
        weight = 500,
      })
      surface.SetFont("PartyGunHudFont")
      local cwidth, cheight = surface.GetTextSize(CurrLine)
      local nwidth, nheight = surface.GetTextSize(NextLine)
      local CenterW = ScrW()*0.5
      local cCenterH = ScrH()*0.25
      local nCenterH = cCenterH + 64
      local RectBorder = 16 --pt
      surface.SetTextColor(255, 0, 0, 255)
      surface.SetTextPos(CenterW-(cwidth/2), cCenterH-(cheight/2))
      surface.DrawText(CurrLine)
      surface.SetTextColor(180, 180, 180, 255)
      surface.SetTextPos(CenterW-(nwidth/2), nCenterH-(nheight/2))
      surface.DrawText(NextLine)
      local width = cwidth
      local height = cheight+nheight+64
      local CenterH = (cCenterH+nCenterH)/2
      if nwidth > cwidth then
        width = nwidth
      end
      surface.SetDrawColor(0,0,0,100)
      surface.DrawRect(CenterW-(width/2)-RectBorder, CenterH-(height/2)-RectBorder, width+(2*RectBorder), height+(2*RectBorder) )
    end)
    if NextLine == "*Song is ending*" then
      timer.Remove("displayText")
      timer.Simple(5.0, function()
        hook.Remove("HUDPaint", "PartyGunHUDStarter")
      end)
    end
  end)
end

Im pretty sure the crashes are caused by the function surface.CreateFont( "PartyGunHudFont" that runs on every frame when PartyGunHUDStarter hook is activated.
As stated on the wiki this can cause crashes.
Font should only be created once when lua file is loaded.

1 Like

Welp, new person doing newbie-mistakes here…
Thanks for the time reading through the script. Already changed the code and will test as soon as someone is available for it. Would have taken me quite some time to find this.

EDIT: That definitely fixed it, thank you very much!